num_images_group = np.size(t_group) # Read the image hdulist = fits.open(dir_images + '/' + file) data = hdulist['PRIMARY'].data stretch = astropy.visualization.PercentileInterval(10) # PI(90) scales array to 5th .. 95th %ile. # Create the backplanes (planes, descs) = hbt.compute_backplanes(dir_images + '/' + file) am = planes['Ang_Metis'] dx_total = ( t_group['dx_opnav'][index_image] ) dy_total = ( t_group['dy_opnav'][index_image]) (dx_total, dy_total) =(52,70) am_roll = np.roll(np.roll(am, -dy_total, axis=0), -dx_total, axis=1) # Plot it data_stretch = stretch(hbt.remove_sfit(data,degree=5)) plt.imshow(data_stretch) plt.show() plt.imshow( data_stretch * (am_roll > 0.0001)) plt.show()
if __name__ == '__main__': import astropy.visualization stretch_percent = 90 stretch = astropy.visualization.PercentileInterval(stretch_percent) # PI(90) scales to 5th..95th %ile. arr = hbt.nh_get_straylight_median(6,[120,121,122,123,124],do_sfit=True, power=5) plt.imshow(stretch(arr)) arr = hbt.nh_get_straylight_median(6,[120,121,122,123,124],do_sfit=True, power=10) plt.imshow(stretch(arr)) arr = hbt.nh_get_straylight_median(6,[120,121,122,123,124],do_sfit=False, power=2) plt.imshow(arr) arr = hbt.nh_get_straylight_median(6,[120,121,122,123,124],do_sfit=True, power=5) plt.imshow(arr) arr_bg = hbt.nh_get_straylight_median(7,[49,50,51],do_sfit=False) arr_image = hbt.nh_get_straylight_median(7,[52], do_sfit=False) plt.imshow(stretch(hbt.remove_sfit(arr_image - arr_bg,degree=1))) # Now try one for real, from 8/0-48 sequence. # There are a lot of ring artifacts left we wnt to get rid of. arr = hbt.nh_get_straylight_median(8,hbt.frange(0,48), do_sfit=True, power=5) plt.imshow(stretch(hbt.remove_sfit(arr,degree=1)))
# Read and plot the LHS image of a 1x3 mosaic of Gossamer ring images. # This is a lot of lines of code, but most of it is just stacking images and scaling, which should be rewritten # into a function. index_group = 6 index_image = hbt.frange(181,184).astype(int) # Which frame from the group do we extract? image_stray = hbt.nh_get_straylight_median(6, hbt.frange(185,188)) # image_arr = np.zeros((1024, 1024, 4)) for i in range(4): image = hbt.read_lorri(t_group[index_image[i]]['Filename'], autozoom=True) image_arr[:,:,i] = image image_sum = np.sum(image_arr,axis=2) image_sum_sfit = hbt.remove_sfit(image_sum, 5) (image_stray_scale,junk) = hbt.normalize_images(image_stray, image_sum_sfit) final = image_sum_sfit - image_stray_scale final = hbt.remove_brightest(final, 0.95, symmetric=True) final = hbt.remove_sfit(final,5) ax=plt.subplot(1,3,2) name = (t_group[index_image[0]]['Shortname'] + ' .. ' + t_group[index_image[3]]['Shortname']).replace('lor_', '').replace('_0x633_sci_1.fit','') plt.title(name) ax.get_xaxis().set_visible(False) ax.get_yaxis().set_visible(False) plt.imshow(final) #plt.show()
#stop image_stray = hbt.nh_get_straylight_median(index_group, hbt.frange(8,15)) nx = np.shape(image)[0] ny = np.shape(image)[1] #image_stray = '/Users/throop/data/NH_Jring/out/straylight_median_g7_n8..15_sfit5,5.pkl' xvals = range(nx) yvals = range(ny) (x,y) = np.meshgrid(xvals,yvals) stretch = astropy.visualization.PercentileInterval(90) # PI(90) scales array to 5th .. 95th %ile. image_s5 = hbt.remove_sfit(image,5) plt.rcParams['figure.figsize'] = 5,5 #plt.subplot(2,2,1) fig = plt.figure() ax = fig.gca(projection='3d') surf = ax.plot_surface(x, y, stretch(image_stray), rstride=10, cstride=10, cmap=cm.coolwarm, linewidth=0, antialiased=False) plt.show() #plt.subplot(2,2,2) plt.imshow(stretch(image_stray)) plt.show() #plt.subplot(2,2,3)
file_backplane = file_image.replace('.fit', '_planes.pkl') file_short = file_image.replace('.fit', '').replace('_sci', '').replace('_opnav', '')[0:-8] #============================================================================== # Read image and all associated files #============================================================================== # Read the image and header power_sfit = 5 im = hbt.read_lorri(dir_images + file_image) im = hbt.lorri_destripe(im) im = hbt.remove_sfit(im, power_sfit) header = hbt.get_image_header(dir_images + file_image) # Load the WCS coords (so we can overlay the ring) with warnings.catch_warnings(): warnings.simplefilter("ignore") w = WCS(dir_images + file_image) # Process headaer dx_pix = header['NAXIS1'] dy_pix = header['NAXIS2'] et = header['SPCSCET']
'mpf_0299793106_0x539_sci_2.fit', # O_RING_DEP_MVICFRAME_202 'mpf_0308706966_0x539_sci_6.fit'] # O_RING_DEP_MVICFRAME_305 stretch_percent = 90 hdulist = [] stretch = astropy.visualization.PercentileInterval(stretch_percent) # PI(90) scales to 5th..95th %ile. plt.set_cmap('Greys_r') for file in files: hdulist.append(fits.open(dir + file)) plt.imshow(hbt.remove_sfit(stretch(hdulist[2]['PRIMARY'].data[0,:,:]),2)) # Load the mosaics files2 = ['ringdep_mos_v1_wcs.fits', 'mvic_d202_mos_v1.fits', 'mvic_d211_mos_v1_wcs.fits', 'mvic_d305_sum_mos_v1_wcs.fits'] hdulist2 = [] for file in files2: hdulist2.append(fits.open(dir + file)) for hdu in hdulist2: print("Size = ") plt.show()
plt.subplot(1, 4, 1) plt.imshow(stretch(image_raw)) plt.title('stretch(image_raw)') plt.subplot(1, 4, 2) plt.imshow(stretch(im)) plt.imshow(mask_objects, alpha=0.2, cmap='plasma') plt.title('stretch(image_raw), overlay w mask_objects') plt.subplot(1, 4, 3) plt.imshow(mask_objects) plt.title('mask_objects') plt.show() plt.subplot(1, 4, 1) plt.imshow(stretch(hbt.remove_sfit(image_raw, degree=5)), cmap='plasma') plt.title('hbt.remove_sfit(image_raw)') plt.subplot(1, 4, 3) plt.imshow(stretch( hbt.remove_sfit(image_raw, degree=5, mask=np.logical_not(mask_objects))), cmap='plasma') plt.title('hbt.remove_sfit(mask=mask_objects)') plt.show() ### # method = 'String'
def nh_jring_process_image(image_raw, method, vars, index_group=-1, index_image=-1, mask_sfit=None): """ Return image with stray light removed. Flux is preserved and no clipping is done. Parameters ----- image_raw: NumPy array with the data image. method: Method of background subtraction, which can be 'Next', 'Prev', 'Polynomial', 'String', 'None', 'Grp Num Frac Pow', etc. In general 'String' is the most flexible, and recommended. It can be things like "5/0-10 r3 *2 mask_7_10': - Make a median of Group 5, Images 0-10 - Rotate them all by 270 degrees - Scale it to the data image - Multiply background image by 2 - Subtract data - background - Remove a 5th degree polynomial from the result [always done, regardless] - Load the Photoshop-created mask file "mask_7_10" and incorporate via a tuple - Return final result vars: The argument to the 'method'. Can be an exponent, a file number, a string, etc -- arbitrary, as needed. index_group: Index of current image. Not used except for Next/Prev. index_image: Index of current group. Not used except for Next/Prev. mask_sfit: An optional mask to be applied when doing the sfit. Ony pixels with True will be used. This is to mask out satellites, stars, CRs, etc. so they don't affect the sfit(). """ stretch_percent = 90 stretch = astropy.visualization.PercentileInterval( stretch_percent) # PI(90) scales to 5th..95th %ile. # Load the arrays with all of the filenames dir_out = '/Users/throop/Data/NH_Jring/out/' file_pickle = dir_out + 'nh_jring_read_params_571.pkl' # Filename to get filenames, etc. lun = open(file_pickle, 'rb') t = pickle.load(lun) lun.close() # Initialize variables DO_MASK = False # We set this based on whether a mask is passed in or not dir_mask_stray = dir_out.replace( 'out', 'masks') # Directory where the mask files are # Process the group names. Some of this is duplicated logic -- depends on how we want to use it. groups = astropy.table.unique(t, keys=(['Desc']))['Desc'] if (index_group != -1): # Only do this if we actually passed a group in groupmask = (t['Desc'] == groups[index_group]) t_group = t[groupmask] # Look up the filename, in case we need it. file_image = t_group['Filename'][index_image] if (method == 'Previous'): file_prev = t_group['Filename'][index_image - 1] # print "file = " + filename print("file_prev = " + file_prev) image_bg = hbt.read_lorri(file_prev, frac_clip=1.0, bg_method='None') image_fg = image_raw image = image_fg - image_bg image_processed = image if (method == 'Next'): file_next = t_group['Filename'][index_image + 1] image_bg = hbt.read_lorri(file_next, frac_clip=1.0, bg_method='None', autozoom=True) image_fg = image_raw image = image_fg - image_bg image_processed = image if (method == 'Median'): # XXX not working yet file_prev = t_group['Filename'][index_image - 1] image_bg = hbt.read_lorri(file_prev, frac_clip=1.0, bg_method='None') image_fg = image_raw image = image_fg - image_bg image_processed = image if (method == 'Polynomial'): power = vars image = image_raw - hbt.sfit( image_raw, power) # Look up the exponenent and apply it image_processed = image if ( method == 'Grp Num Frac Pow' ): # Specify to subtract a specified group#/image#, mult factor, and sfit power. # I thought this would be useful, but it turns out we usually need to subtract # a median of multiple images -- not just one -- so this is not very useful. # Plus, the best power is usually 5, and the best frac can be calc'd # with a linfit. if (np.size(vars) == 0): # If no args passed, just plot the image power = 0 frac = 0 image = image_raw if (np.size(vars) == 1): # One variable: interpret as exponent power = float(vars[0]) frac = 0 image = image_raw image_bg = hbt.sfit(image, power, mask=mask_sfit) image = image - image_bg if (np.size(vars) == 2 ): # Two variables: interpret as group num and file num (grp, num) = vars frac = 1 power = 0 if ( np.size(vars) ) == 3: # Three variables: interpret as group num, file num, fraction (grp, num, frac) = vars power = 0 if (np.size(vars) == 4 ): # Four variables: Group num, File num, Fraction, Exponent (grp, num, frac, power) = vars if int(np.size(vars)) in [2, 3, 4]: grp = int(grp) num = int(num) frac = float(frac) power = int(power) print("group={}, num={}, frac={}".format(grp, num, frac)) # print "Group = {}, num{}, Name = {}".format(name_group, num, name) name_group = groups[grp] groupmask = t['Desc'] == name_group group_tmp = t[groupmask] filename_bg = group_tmp['Filename'][num] image_fg = image_raw image_bg = hbt.read_lorri(filename_bg, frac_clip=1, bg_method='None') image = image_fg - float(frac) * image_bg image = image - hbt.sfit(image, power, mask=mask_sfit) image_processed = image # ============================================================================= # Do method 'None' (trivial) # ============================================================================= if (method == 'None'): image_processed = image = image_raw #============================================================================== # Do method 'String'. Complicated, but most useful. # # Parse a string like "6/112-6/129", or "129", or "6/114", or "124-129" # or "6/123 - 129" or "6/123-129 r1 *0.5 p4 mask_7_12" # or "". # # Except for the group and image number, the order of thse does not matter. #============================================================================== #### # As of 8-July-2017, this is the one I will generally use for most purposes. # # 'String' does this: # o Subtract the bg image made by combining the named frames, and rotating and scaling as requested (optional) # o Apply a mask file (optional) # o Subtract a polynomial (optional). ** As of 17-Nov-2017, sfit is applied only to masked pixels, not full image. # #### if (method == 'String'): str = vars # ============================================================================= # Parse any rotation angle -- written as "r90" -- and remove from the string # ============================================================================= angle_rotate_deg = 0 match = re.search('(r[0-9]+)', str) if match: angle_rotate_deg = int(match.group(0).replace( 'r', '')) # Extract the rotation angle str = str.replace(match.group(0), '') # Remove the whole phrase from string if ( np.abs(angle_rotate_deg) ) <= 10: # Allow value to be passed as (1,2,3) or (90, 180, 270) angle_rotate_deg *= 90 # Determine how much the bg frame should be scaled, to match the data frame. This is just a multiplicative # factor that very crudely accomodates for differences in phase angle, exptime, etc. # ============================================================================= # Parse any stray multiplication factor -- written as "*3" -- and remove from the string # Multiplicative factor is used to scale the stray light image to be removed, up and down (e.g., up by 3x) # ============================================================================= factor_stray_default = 1 # Define the default multiplicative factor factor_stray = factor_stray_default match = re.search( '(\*[0-9.]+)', str ) # Match *3 *0.4 etc [where '*' is literal, not wildcard] if match: factor_stray = float(match.group(0).replace( '*', '')) # Extract the multiplicative factor str = str.replace(match.group(0), '').strip() # Remove phrase from the string # ============================================================================= # Parse any mask file -- written as "mask_7_0" -- and remove from the string # ============================================================================= # # This mask is a fixed pattern, read from a file, for stray light etc. # It is *not* for stars or satellites, which are calculated separately. # # To make these mask files, the steps are... # Maskfile is using same name structure as for the summed bg straylight images -- e.g., 8/0-48. # # True = good pixel. False = bad. file_mask_stray = None match = re.search('(mask[0-9a-z._\-]+)', str) print("Str = {}, dir_mask_stray = {}".format(str, dir_mask_stray)) if match: file_mask_stray = dir_mask_stray + match.group( 0) + '.png' # Create the filename DO_MASK = True str = str.replace(match.group(0), '').strip() # Remove the phrase from the string # ============================================================================= # Parse any polynomial exponent -- written as 'p5' # This is the polynomial removed *after* subtracting Image - Stray # ============================================================================= poly_after_default = 0 # Define the default polynomial to subtract. # I could do 5, or I could do 0. poly_after = poly_after_default match = re.search('(p[0-9]+)', str) if match: poly_after = int(match.group(0).replace( 'p', '')) # Extract the polynomal exponent str = str.replace(match.group(0), '').strip() # Remove the phrase from the string # ============================================================================= # Now parse the rest of the string # ============================================================================= # The only part that is left is 0, 1, or 2 integers, which specify the stray light file to extract # They must be in the form "7/12-15", or "7/12" or "12" str2 = str.replace('-', ' ').replace('/', ' ').replace( 'None', '') # Get rid of any punctuation vars = np.array( str2.split(), dtype=int ) # With no arguments, split() breaks at any set of >0 whitespace chars. # ============================================================================= # Now load the appropriate stray light image, based on the number of arguments passed # ============================================================================= do_sfit_stray = False # Flag: When constructing the straylight median file, do we subtract polynomial, or not? # # Usually we want False. ie, want to do: # out = remove_sfit(raw - stray) # not # out = remove_sfit(raw) - remove_sfit(stray) if (np.size(vars) == 0): # "<no arguments>" image = image_raw image_processed = image image_stray = 0 * image if (np.size(vars) == 1): # "12" -- image number image_stray = hbt.nh_get_straylight_median( index_group, [int(vars[0])], do_sfit=do_sfit_stray) # "122" -- assume current group if (np.size(vars) == 2): # "7-12" -- image range image_stray = hbt.nh_get_straylight_median( index_group, hbt.frange(int(vars[0]), int(vars[1])).astype('int'), do_sfit=do_sfit_stray) # "122-129" # -- assume current group if (np.size(vars) == 3): # "7/12-20" -- group, plus image range image_stray = hbt.nh_get_straylight_median( int(vars[0]), hbt.frange(vars[1], vars[2]).astype('int'), do_sfit=do_sfit_stray) # "5/122 - 129" if (np.size(vars) == 4 ): # "7/12 - 7/20" (very wordy -- don't use this) image_stray = hbt.nh_get_straylight_median( int(vars[0]), hbt.frange(vars[1], vars[3]).astype('int'), do_sfit=do_sfit_stray) # "6/122 - 6/129" # Adjust the stray image to be same size as original. # Sometimes we'll have a 4x4 we want to use as stray model for 1x1 image -- this allows that. # When we resize it, also adjust the flux (e.g., by factor of 16). dx_stray = hbt.sizex(image_stray) dx_im = hbt.sizex(image_raw) ratio = dx_im / dx_stray if (dx_stray < dx_im): image_stray = scipy.ndimage.zoom(image_stray, ratio) / ( ratio**2) # Enlarge the stray image if (dx_stray > dx_im): image_stray = scipy.ndimage.zoom(image_stray, ratio) / ( ratio**2) # Shrink the stray image #============================================================================= # Now that we have parsed the string, do the image processing #============================================================================= # Load the Photoshop stray mask file, if it exists. Otherwise, make a blank mask of True. if file_mask_stray: try: mask_stray = imread( file_mask_stray ) > 128 # Read file. Mask PNG file is 0-255. Convert to boolean. print("Reading mask file {}".format(file_mask_stray)) if ( len(np.shape(mask_stray)) > 2 ): # If Photoshop saved multiple planes, then just take first mask_stray = mask_stray[:, :, 0] except IOError: # If mask file is missing print("Stray light mask file {} not found".format( file_mask_stray)) else: mask_stray = np.ones(np.shape(image_raw), dtype=bool) # Load the object mask. This masks out stars and satellites, which should not have sfit applied to them. file_objects = os.path.basename(file_image).replace( '.fit', '_objects.txt') mask_objects = nh_jring_mask_from_objectlist(file_objects) mask_objects = np.logical_not( mask_objects) # Make so True = good pixel # Merge the two masks together mask = np.logical_and( mask_objects, mask_stray) # Output good if inputs are both good # Rotate the stray light image, if that has been requested # [this probably doesn't work, but that's fine -- I didn't end up using this.] image_stray = np.rot90( image_stray, angle_rotate_deg / 90) # np.rot90() takes 1, 2, 3, 4 = 90, 180, 270, 360. # Subract the final background image from the data image image_processed = image_raw - factor_stray * image_stray # print("Removing bg. factor = {}, angle = {}".format(factor_stray, angle_rotate_deg)) # Apply the mask: convert any False pixels to NaN in prep for the sfit image_masked = image_processed.copy() image_masked[mask == False] = math.nan frac_good = np.sum(mask) / (np.prod(np.shape(mask))) print("Applying mask, fraction good = {}".format(frac_good)) # Remove a polynomial from the result. This is where the mask comes into play. # XXX NB: I think the logic here could be cleaned up. sfit() now allows a mask= argument , # but it must not have when I wrote this code. sfit_masked = hbt.sfit(image_masked, poly_after) image_processed = image_processed - sfit_masked print("Removing sfit {}".format(poly_after)) # Plot the masks and sfits, for diagnostics do_plot_masks = False if do_plot_masks: plt.subplot(1, 3, 1) plt.imshow(stretch(mask)) plt.title('mask') plt.subplot(1, 3, 2) plt.imshow(stretch(image_masked)) plt.title('image_masked') plt.subplot(1, 3, 3) plt.imshow(sfit_masked) plt.title('sfit_masked') plt.show() # ============================================================================= # END OF CASE STATEMENT FOR METHODS # ============================================================================= # Remove a small bias offset between odd and even rows ('jailbars') # This might be better done before the sfit(), but in reality probably doesn't make a difference. image_processed = hbt.lorri_destripe(image_processed) # If requested: plot the image, and the background that I remove. # Plot to Python console, not the GUI. # Test stretching here # We use astropy's stretching here, rather than matplotlib's norm= keyword. The basic idea of both of these # is the same, but I know that astropy has a percentile stretch available. DO_DIAGNOSTIC = False if (DO_DIAGNOSTIC): stretch = astropy.visualization.PercentileInterval( 90) # PI(90) scales array to 5th .. 95th %ile plt.rcParams['figure.figsize'] = 16, 6 # Column 1: raw image im = image_raw im = hbt.remove_sfit(im, degree=5) plt.subplot(1, 3, 1) # vertical, horizontal, index plt.imshow(stretch(hbt.remove_sfit(im, degree=5))) plt.title('remove_sfit(image_raw, degree=5), mean=' + hbt.trunc(np.mean(im), 3)) plt.colorbar() # Column 2: Stray only. This will throw an error if we haven't read in a stray light file -- just ignore it. plt.subplot(1, 3, 2) try: plt.imshow(stretch(hbt.remove_sfit( image_stray, degree=5))) # This won't do much since it is already applied except UnboundLocalError: print("No stray light to subtract") plt.title('remove_sfit(stray_norm, degree=5), mean=' + hbt.trunc(np.mean(im), 3)) # Column 3: raw - stray plt.subplot(1, 3, 3) try: im = hbt.remove_sfit(image_raw - image_stray, degree=5) plt.imshow(stretch(im)) except UnboundLocalError: print("No stray light to subtract") plt.title('remove_sfit(image_raw - image_stray, degree=5), med ' + hbt.trunc(np.median(im), 3)) plt.show() # Now return the array. If we have a mask, then we return it too, as a tuple if (DO_MASK): # If we loaded a mask return (image_processed, mask) else: return image_processed
images = [] for index_image in index_imagesets_i: t_image = t_group[index_image] # Grab this, read-only, since we use it a lot. file_in = t_image['Filename'] image = hbt.read_lorri(file_in, frac_clip = 1., bg_method = 'None') # Read the image image = hbt.lorri_destripe(image) images.append(image) # Stuff this image onto the end of a list. images = np.array(images) # Now we have a 3D Numpy array with all the image data images_sum = np.sum(images,axis=0) # And sum down into a 2D array. images_proc = stretch(hbt.remove_sfit(images_sum, degree=power)) # plt.imshow(images_sum) # plt.title("{}/{}-{} RAW".format(index_group, index_start, index_end)) # plt.show() plt.imshow(images_proc) plt.title("{}/{}-{} - sfit({})".format(index_group, index_start, index_end, power)) plt.show() imsave(file_out, images_proc) # # lun = open(file_out, 'wb') # binary mode is important # w = png.Writer(1024, 1024, greyscale=True) # w.write(lun, 255*image_proc) # lun.close()
print(f'Position of LORRI center in Jup coords: {pt_closest_jup_jup}') print(f'Position of LORRI center: Horiz = {dist_jup_center_horizontal_rj:.2}, ' + f'Vert = {dist_jup_center_vertical_rj:.2}') # Set the 'extent', which is the axis values for the X and Y axes for an imshow(). # We set the y range to be the same as X, to convince python to keep the pixels square. extent = [dist_jup_center_rj_range[0], dist_jup_center_rj_range[1],\ dist_jup_center_rj_range[0], dist_jup_center_rj_range[1]] # Plot the image, sfit subtracted plt.subplot(2,2,2) degree=5 im_sum /= len(index_images) # We have just summed, so now divide so as to preserve DN of original. im_sum_s = hbt.remove_sfit(im_sum,degree=degree) # _s = sfit subtracted plt.imshow(stretch(im_sum_s), origin='lower', extent=extent) # plt.gca().get_xaxis().set_visible(False) plt.gca().get_yaxis().set_visible(False) plt.title(f'{index_group}/{np.amin(index_images)}-{np.amax(index_images)} - p{degree}') # Plot the image, with better subtraction plt.subplot(2,2,3) method = 'String' vars = '6/63-70 p5' # index_image = 124 # index_group = 6 im_process = hbt.nh_jring_process_image(im_sum, method, vars, index_group=index_group, index_image=index_image, mask_sfit=None)
def nh_create_straylight_median(index_group, index_files, do_fft=False, do_sfit=True, power=5): """ This takes a set of related observations, and creates a median image of all of them, in a form useful for straylight removal. For now this routine is hard-coded to NH J-ring, but it could be generalized later. This routine actually does the calculation. For a user, should call NH_GET_STRAYLIGHT_MEDIAN, rather than the current function, which will avoid having to deal with filenames. Although the filename is called 'median', this function uses different techniques to calculate the best background image, including percentiles, means, etc -- not just a median. This routine does not use a photoshop mask. That can be used elsewhere. This routine also does not use an object mask. But, other stray light processing parts do that. """ # o index_group: What set of observations, grouped by 'Desc' field. Integer. # o index_files: Numpy array list of the files. Cannot be a scalar. # o do_fft: Flag. For the final step, do we use an FFT? [NOT IMPLEMENTED] # o do_sfit: Flag. Do we use an sfit (ie, polynomial fit)? # o power: Exponent for sfit, to be applied at end and subtracted # # This routine returns the array itself, and a recommended base filename. It does not write it to disk. do_debug = True do_destripe = True # Flag: Do we apply LORRI de-striping to each frame? do_normalize = True stretch = astropy.visualization.PercentileInterval(90) # PI(90) scales array to 5th .. 95th %ile if do_debug: print("nh_create_straylight_median: {}/{}-{}".format(index_group, index_files[0], index_files[-1])) file_pickle = '/Users/throop/Data/NH_Jring/out/nh_jring_read_params_571.pkl' # Filename to read to get filenames, etc. lun = open(file_pickle, 'rb') t = pickle.load(lun) lun.close() # Process the group names. Some of this is duplicated logic -- depends on how we want to use it. groups = astropy.table.unique(t, keys=(['Desc']))['Desc'] groupmask = (t['Desc'] == groups[index_group]) t_group = t[groupmask] # Create the output arrays num_files = np.size(index_files) header = hbt.get_image_header(t_group['Filename'][index_files[0]]) # Get dimensions of first frame in the series dx_0 = header['NAXIS1'] dy_0 = header['NAXIS2'] print("For image 0, dx={}".format(dx_0)) frame_arr = np.zeros((num_files, dx_0, dy_0)) frame_sfit_arr = np.zeros((num_files, dx_0, dy_0)) frame_ffit_arr = np.zeros((num_files, dx_0, dy_0)) # Read frames in to make a median for i,n in enumerate(index_files): file = t_group['Filename'][n] # Look up filename print("Reading: " + file) frame = hbt.read_lorri(file,frac_clip = 1) if do_destripe: frame = hbt.lorri_destripe(frame) if (hbt.sizex(frame) != dx_0): print("For image {}, dx={}, dy={}".format(i, hbt.sizex(frame), hbt.sizey(frame))) print("Error: mixed sizes in this straylight series. Aborting.") # raise ValueError('MultipleSizes') if (np.shape(frame)[0] == 256): # # Resize the image to 1024x1024, if it is a 4x4 frame. # scipy.misc.imresize should do this, and has various interpolation schemes, but truncates to integer (?!) # because it is designed for images. So instead, use scipy.ndimage.zoom, which uses cubic spline. # frame2 = scipy.ndimage.zoom(frame, 4) frame = frame2 if (np.shape(frame)[0] == 1024): frame2 = scipy.ndimage.zoom(frame, 0.25) frame = frame2 frame_arr[i,:,:] = frame # frame_sfit_arr[i,:,:] = frame - hbt.sfit(frame, power) # Calculate the sfit to each individual frame # frame_ffit_arr[i,:,:] = frame - hbt.ffit(frame) # Now take the median! frame_med = np.median(frame_arr, axis=0) # Now normalize if necessary if do_normalize: frame_arr_norm = frame_arr.copy() for i in range(num_files): (frame_arr_norm[i], r) = hbt.normalize_images(frame_arr[i], frame_med) frame_arr = frame_arr_norm # Make a plot of the mean and median of each file, to see how the normalization worked mean_arr = np.zeros(num_files) median_arr = np.zeros(num_files) for i in range(num_files): mean_arr[i] = np.nanmean(frame_arr[i]) median_arr[i] = np.nanmedian(frame_arr[i]) plt.plot(mean_arr, label='mean') plt.plot(median_arr, label='median') plt.legend() plt.show() # Take the median, again frame_med = np.median(frame_arr, axis=0) # Now that we have the median for each pixel... take the median of pixels below the median. frame_arr_step_2 = frame_arr.copy() for j in range(hbt.sizex(frame_arr)): frame_arr_step_2[j][frame_arr_step_2[j] > frame_med] = np.nan frame_med_step_2 = np.nanmedian(frame_arr_step_2, axis=0) # # frame_arr_step_3 = frame_arr_step_2.copy() # for j in range(hbt.sizex(frame_arr)): # frame_arr_step_3[j][frame_arr_step_3[j] > frame_med_step_2] = np.nan # # frame_med_step_3 = np.nanmedian(frame_arr_step_2, axis=0) # # frame_med_step3 = np.percentile(frame_arr, 10, axis=0) # # hbt.figsize(25,25) # plt.subplot(1,3,1) # plt.imshow(stretch(hbt.remove_sfit(frame_med,degree=5)), cmap='plasma') # plt.title('Median') # # plt.subplot(1,3,2) # plt.imshow(stretch(hbt.remove_sfit(frame_med_step_2,degree=5)), cmap='plasma') # plt.title('Median of pixels < median') # # plt.subplot(1,3,3) # plt.imshow(stretch(hbt.remove_sfit(frame_med_step_3,degree=5)), cmap='plasma') # plt.title('Median of pixels < pixels < median') # plt.show() # hbt.figsize() #%%% ###### # Now just do some experimentation. I am just trying a bunch of things, to try to get the best signal here. # In theory a median will work well. The problem is that this just doesn't exclude all the ring. # So I tried taking 30th percentile. Works better. # Tried taking 1st percentile (ie, faintest value at each pixel). But this still had a ring, dammit! # I think it must be because a few images are anomalously faint, throwing off the statistics. # # The method right here uses a very ad-hoc percentile method. There is no reason it should work. # I've just found that it does. ###### hbt.figsize(10,10) frame_percentile = np.percentile(frame_arr, 99, axis=0) plt.imshow(stretch(hbt.remove_sfit(frame_percentile,degree=5)), cmap='plasma') # Take the mmedians at a bunch of different percentiles frame_p10 = np.percentile(frame_arr, 10, axis=0) frame_p20 = np.percentile(frame_arr, 20, axis=0) frame_p30 = np.percentile(frame_arr, 30, axis=0) frame_p40 = np.percentile(frame_arr, 40, axis=0) frame_p50 = np.percentile(frame_arr, 50, axis=0) frame_p60 = np.percentile(frame_arr, 60, axis=0) frame_p70 = np.percentile(frame_arr, 70, axis=0) frame_p80 = np.percentile(frame_arr, 80, axis=0) frame_p90 = np.percentile(frame_arr, 90, axis=0) frames_pall = np.array([frame_p10, frame_p20, frame_p30, frame_p40, frame_p50, frame_p60, frame_p70, frame_p80]) frames_pall = np.array([frame_p10, frame_p20]) # Mean these frames all together frames_pall_med = np.mean(frames_pall,axis=0) plt.imshow(stretch(hbt.remove_sfit(frames_pall_med))) plt.title('frames_pall_med') plt.show() frame_med = frames_pall_med # Convolve the output array with a gaussian, to smooth it # *** Did a test. Doing this gaussian convolution to smooth it makes no difference. do_smooth = True # Smooth the output array with a gaussian kernel? if do_smooth: kernel = Gaussian2DKernel(3) frame_med_convolve = astropy.convolution.convolve(frame_med,kernel, boundary='extend') # Repair the edges, by copying data back from the orignal source image, pre-convolution # Copy from a few kernel widths away, to be sure we get it all. frame_med_convolve[0:12,:] = frame_med[0:12,:] frame_med_convolve[-12:,:] = frame_med[-12:,:] frame_med_convolve[:,0:12] = frame_med[:,0:12] frame_med_convolve[:,-12:] = frame_med[:,-12:] # Save into the ouput array frame_med = frame_med_convolve # frame_med = frame_p20 #%%% frame_med_sfit = hbt.remove_sfit(frame_med, degree=power) # Take median using sfit images # frame_ffit_med = np.median(frame_ffit_arr, axis=0) # Take median using fft images # Do a very small removal of hot pixels. Between 0.99 and 0.999 seems to be fine. Make as small # as possible, but something is often necessary # frame_sfit_med = hbt.remove_brightest(frame_sfit_med, 0.999) # frame_ffit_med = hbt.remove_brightest(frame_sfit_med, 0.999) # file_base = hbt.nh_create_straylight_median_filename(index_group, index_files, do_fft=do_fft, do_sfit=do_sfit, power=power) if (do_fft): raise ValueError('Sorry, do_ffit not implemented') return -1 if (do_sfit): return (frame_med_sfit, file_base) else: return (frame_med, file_base)
if (do_fft): raise ValueError('Sorry, do_ffit not implemented') return -1 if (do_sfit): return (frame_med_sfit, file_base) else: return (frame_med, file_base) # ============================================================================= # End of Function # ============================================================================= if (__name__ == '__main__'): stretch = astropy.visualization.PercentileInterval(90) # PI(90) scales array to 5th .. 95th %ile print("Testing...") index_group = 8 index_files = hbt.frange(0,47) do_fft = False power = 5 do_sfit = False (arr,mask) = nh_create_straylight_median(index_group, index_files, do_fft=do_fft, do_sfit=do_sfit, power=power) plt.set_cmap('plasma') plt.imshow(stretch(hbt.remove_sfit(arr, degree=5)))
##### # Just a scratch spot to test star search, etc. from photutils import daofind from astropy.stats import sigma_clipped_stats from photutils import find_peaks DO_TEST = False if (DO_TEST): num = 200 # Number of brightest objects to keep image_s = hbt.remove_sfit(image,4) mean, median, std = sigma_clipped_stats(image, sigma=3.0, iters=5) sources = daofind(image, fwhm=2.0, threshold=2.*std) threshold = median + (10.0 * std) # Ten sigma tbl = find_peaks(image, threshold, box_size=5) sources.sort('flux') # Sort in-place tbl.sort('peak_value') if (num > 0): index_start = -num else: index_start = 0
def nh_jring_process_image(image_raw, method, vars, index_group=-1, index_image=-1, mask_sfit=None): """ Return image with stray light removed. Flux is preserved and no clipping is done. Parameters ----- image_raw: NumPy array with the data image. method: Method of background subtraction, which can be 'Next', 'Prev', 'Polynomial', 'String', 'None', 'Grp Num Frac Pow', etc. In general 'String' is the most flexible, and recommended. It can be things like "5/0-10 r3 *2 mask_7_10': - Make a median of Group 5, Images 0-10 - Rotate them all by 270 degrees - Scale it to the data image - Multiply background image by 2 - Subtract data - background - Remove a 5th degree polynomial from the result [always done, regardless] - Load the Photoshop-created mask file "mask_7_10" and incorporate via a tuple - Return final result vars: The argument to the 'method'. Can be an exponent, a file number, a string, etc -- arbitrary, as needed. index_group: Index of current image. Not used except for Next/Prev. index_image: Index of current group. Not used except for Next/Prev. mask_sfit: An optional mask to be applied when doing the sfit. Ony pixels with True will be used. This is to mask out satellites, stars, CRs, etc. so they don't affect the sfit(). """ stretch_percent = 90 stretch = astropy.visualization.PercentileInterval(stretch_percent) # PI(90) scales to 5th..95th %ile. # Load the arrays with all of the filenames dir_out = '/Users/throop/Data/NH_Jring/out/' file_pickle = dir_out + 'nh_jring_read_params_571.pkl' # Filename to get filenames, etc. lun = open(file_pickle, 'rb') t = pickle.load(lun) lun.close() # Initialize variables DO_MASK = False # We set this based on whether a mask is passed in or not dir_mask_stray = dir_out.replace('out','masks') # Directory where the mask files are # Process the group names. Some of this is duplicated logic -- depends on how we want to use it. groups = astropy.table.unique(t, keys=(['Desc']))['Desc'] if (index_group != -1): # Only do this if we actually passed a group in groupmask = (t['Desc'] == groups[index_group]) t_group = t[groupmask] # Look up the filename, in case we need it. file_image = t_group['Filename'][index_image] if (method == 'Previous'): file_prev = t_group['Filename'][index_image-1] # print "file = " + filename print("file_prev = " + file_prev) image_bg = hbt.read_lorri(file_prev, frac_clip = 1.0, bg_method = 'None') image_fg = image_raw image = image_fg - image_bg image_processed = image if (method == 'Next'): file_next = t_group['Filename'][index_image+1] image_bg = hbt.read_lorri(file_next, frac_clip = 1.0, bg_method = 'None', autozoom=True) image_fg = image_raw image = image_fg - image_bg image_processed = image if (method == 'Median'): # XXX not working yet file_prev = t_group['Filename'][index_image-1] image_bg = hbt.read_lorri(file_prev, frac_clip = 1.0, bg_method = 'None') image_fg = image_raw image = image_fg - image_bg image_processed = image if (method == 'Polynomial'): power = vars image = image_raw - hbt.sfit(image_raw, power) # Look up the exponenent and apply it image_processed = image if (method == 'Grp Num Frac Pow'): # Specify to subtract a specified group#/image#, mult factor, and sfit power. # I thought this would be useful, but it turns out we usually need to subtract # a median of multiple images -- not just one -- so this is not very useful. # Plus, the best power is usually 5, and the best frac can be calc'd # with a linfit. if (np.size(vars) == 0): # If no args passed, just plot the image power = 0 frac = 0 image = image_raw if (np.size(vars) == 1): # One variable: interpret as exponent power = float(vars[0]) frac = 0 image = image_raw image_bg = hbt.sfit(image, power, mask=mask_sfit) image = image - image_bg if (np.size(vars) == 2): # Two variables: interpret as group num and file num (grp, num) = vars frac = 1 power = 0 if (np.size(vars)) == 3: # Three variables: interpret as group num, file num, fraction (grp, num, frac) = vars power = 0 if (np.size(vars) == 4): # Four variables: Group num, File num, Fraction, Exponent (grp, num, frac, power) = vars if int(np.size(vars)) in [2,3,4]: grp = int(grp) num = int(num) frac = float(frac) power = int(power) print("group={}, num={}, frac={}".format(grp, num, frac)) # print "Group = {}, num{}, Name = {}".format(name_group, num, name) name_group = groups[grp] groupmask = t['Desc'] == name_group group_tmp = t[groupmask] filename_bg = group_tmp['Filename'][num] image_fg = image_raw image_bg = hbt.read_lorri(filename_bg, frac_clip = 1, bg_method = 'None') image = image_fg - float(frac) * image_bg image = image - hbt.sfit(image, power, mask=mask_sfit) image_processed = image # ============================================================================= # Do method 'None' (trivial) # ============================================================================= if (method == 'None'): image_processed = image = image_raw #============================================================================== # Do method 'String'. Complicated, but most useful. # # Parse a string like "6/112-6/129", or "129", or "6/114", or "124-129" # or "6/123 - 129" or "6/123-129 r1 *0.5 p4 mask_7_12" # or "". # # Except for the group and image number, the order of thse does not matter. #============================================================================== #### # As of 8-July-2017, this is the one I will generally use for most purposes. # # 'String' does this: # o Subtract the bg image made by combining the named frames, and rotating and scaling as requested (optional) # o Apply a mask file (optional) # o Subtract a polynomial (optional). ** As of 17-Nov-2017, sfit is applied only to masked pixels, not full image. # #### if (method == 'String'): str = vars # ============================================================================= # Parse any rotation angle -- written as "r90" -- and remove from the string # ============================================================================= angle_rotate_deg = 0 match = re.search('(r[0-9]+)', str) if match: angle_rotate_deg = int(match.group(0).replace('r', '')) # Extract the rotation angle str = str.replace(match.group(0), '') # Remove the whole phrase from string if (np.abs(angle_rotate_deg)) <= 10: # Allow value to be passed as (1,2,3) or (90, 180, 270) angle_rotate_deg *= 90 # Determine how much the bg frame should be scaled, to match the data frame. This is just a multiplicative # factor that very crudely accomodates for differences in phase angle, exptime, etc. # ============================================================================= # Parse any stray multiplication factor -- written as "*3" -- and remove from the string # Multiplicative factor is used to scale the stray light image to be removed, up and down (e.g., up by 3x) # ============================================================================= factor_stray_default = 1 # Define the default multiplicative factor factor_stray = factor_stray_default match = re.search('(\*[0-9.]+)', str) # Match *3 *0.4 etc [where '*' is literal, not wildcard] if match: factor_stray = float(match.group(0).replace('*', '')) # Extract the multiplicative factor str = str.replace(match.group(0), '').strip() # Remove phrase from the string # ============================================================================= # Parse any mask file -- written as "mask_7_0" -- and remove from the string # ============================================================================= # # This mask is a fixed pattern, read from a file, for stray light etc. # It is *not* for stars or satellites, which are calculated separately. # # To make these mask files, the steps are... # Maskfile is using same name structure as for the summed bg straylight images -- e.g., 8/0-48. # # True = good pixel. False = bad. file_mask_stray = None match = re.search('(mask[0-9a-z._\-]+)', str) print("Str = {}, dir_mask_stray = {}".format(str, dir_mask_stray)) if match: file_mask_stray = dir_mask_stray + match.group(0) + '.png' # Create the filename DO_MASK = True str = str.replace(match.group(0), '').strip() # Remove the phrase from the string # ============================================================================= # Parse any polynomial exponent -- written as 'p5' # This is the polynomial removed *after* subtracting Image - Stray # ============================================================================= poly_after_default = 0 # Define the default polynomial to subtract. # I could do 5, or I could do 0. poly_after = poly_after_default match = re.search('(p[0-9]+)', str) if match: poly_after = int(match.group(0).replace('p', '')) # Extract the polynomal exponent str = str.replace(match.group(0), '').strip() # Remove the phrase from the string # ============================================================================= # Now parse the rest of the string # ============================================================================= # The only part that is left is 0, 1, or 2 integers, which specify the stray light file to extract # They must be in the form "7/12-15", or "7/12" or "12" str2 = str.replace('-', ' ').replace('/', ' ').replace('None', '') # Get rid of any punctuation vars = np.array(str2.split(), dtype=int) # With no arguments, split() breaks at any set of >0 whitespace chars. # ============================================================================= # Now load the appropriate stray light image, based on the number of arguments passed # ============================================================================= do_sfit_stray = False # Flag: When constructing the straylight median file, do we subtract polynomial, or not? # # Usually we want False. ie, want to do: # out = remove_sfit(raw - stray) # not # out = remove_sfit(raw) - remove_sfit(stray) if (np.size(vars) == 0): # "<no arguments>" image = image_raw image_processed = image image_stray = 0 * image if (np.size(vars) == 1): # "12" -- image number image_stray = hbt.nh_get_straylight_median(index_group, [int(vars[0])], do_sfit=do_sfit_stray) # "122" -- assume current group if (np.size(vars) == 2): # "7-12" -- image range image_stray = hbt.nh_get_straylight_median(index_group, hbt.frange(int(vars[0]), int(vars[1])).astype('int'), do_sfit=do_sfit_stray) # "122-129" # -- assume current group if (np.size(vars) == 3): # "7/12-20" -- group, plus image range image_stray = hbt.nh_get_straylight_median(int(vars[0]), hbt.frange(vars[1], vars[2]).astype('int'), do_sfit=do_sfit_stray) # "5/122 - 129" if (np.size(vars) == 4): # "7/12 - 7/20" (very wordy -- don't use this) image_stray = hbt.nh_get_straylight_median(int(vars[0]), hbt.frange(vars[1], vars[3]).astype('int'), do_sfit=do_sfit_stray) # "6/122 - 6/129" # Adjust the stray image to be same size as original. # Sometimes we'll have a 4x4 we want to use as stray model for 1x1 image -- this allows that. # When we resize it, also adjust the flux (e.g., by factor of 16). dx_stray = hbt.sizex(image_stray) dx_im = hbt.sizex(image_raw) ratio = dx_im / dx_stray if (dx_stray < dx_im): image_stray = scipy.ndimage.zoom(image_stray, ratio) / (ratio**2) # Enlarge the stray image if (dx_stray > dx_im): image_stray = scipy.ndimage.zoom(image_stray, ratio) / (ratio**2) # Shrink the stray image #============================================================================= # Now that we have parsed the string, do the image processing #============================================================================= # Load the Photoshop stray mask file, if it exists. Otherwise, make a blank mask of True. if file_mask_stray: try: mask_stray = imread(file_mask_stray) > 128 # Read file. Mask PNG file is 0-255. Convert to boolean. print("Reading mask file {}".format(file_mask_stray)) if (len(np.shape(mask_stray)) > 2): # If Photoshop saved multiple planes, then just take first mask_stray = mask_stray[:,:,0] except IOError: # If mask file is missing print("Stray light mask file {} not found".format(file_mask_stray)) else: mask_stray = np.ones(np.shape(image_raw),dtype=bool) # Load the object mask. This masks out stars and satellites, which should not have sfit applied to them. file_objects = os.path.basename(file_image).replace('.fit', '_objects.txt') mask_objects = nh_jring_mask_from_objectlist(file_objects) mask_objects = np.logical_not(mask_objects) # Make so True = good pixel # Merge the two masks together mask = np.logical_and(mask_objects, mask_stray) # Output good if inputs are both good # Rotate the stray light image, if that has been requested # [this probably doesn't work, but that's fine -- I didn't end up using this.] image_stray = np.rot90(image_stray, angle_rotate_deg/90) # np.rot90() takes 1, 2, 3, 4 = 90, 180, 270, 360. # Subract the final background image from the data image image_processed = image_raw - factor_stray * image_stray # print("Removing bg. factor = {}, angle = {}".format(factor_stray, angle_rotate_deg)) # Apply the mask: convert any False pixels to NaN in prep for the sfit image_masked = image_processed.copy() image_masked[mask == False] = math.nan frac_good = np.sum(mask) / (np.prod(np.shape(mask))) print("Applying mask, fraction good = {}".format(frac_good)) # Remove a polynomial from the result. This is where the mask comes into play. # XXX NB: I think the logic here could be cleaned up. sfit() now allows a mask= argument , # but it must not have when I wrote this code. sfit_masked = hbt.sfit(image_masked, poly_after) image_processed = image_processed - sfit_masked print("Removing sfit {}".format(poly_after)) # Plot the masks and sfits, for diagnostics do_plot_masks = False if do_plot_masks: plt.subplot(1,3,1) plt.imshow(stretch(mask)) plt.title('mask') plt.subplot(1,3,2) plt.imshow(stretch(image_masked)) plt.title('image_masked') plt.subplot(1,3,3) plt.imshow(sfit_masked) plt.title('sfit_masked') plt.show() # ============================================================================= # END OF CASE STATEMENT FOR METHODS # ============================================================================= # Remove a small bias offset between odd and even rows ('jailbars') # This might be better done before the sfit(), but in reality probably doesn't make a difference. image_processed = hbt.lorri_destripe(image_processed) # If requested: plot the image, and the background that I remove. # Plot to Python console, not the GUI. # Test stretching here # We use astropy's stretching here, rather than matplotlib's norm= keyword. The basic idea of both of these # is the same, but I know that astropy has a percentile stretch available. DO_DIAGNOSTIC = False if (DO_DIAGNOSTIC): stretch = astropy.visualization.PercentileInterval(90) # PI(90) scales array to 5th .. 95th %ile plt.rcParams['figure.figsize'] = 16,6 # Column 1: raw image im = image_raw im = hbt.remove_sfit(im, degree=5) plt.subplot(1,3,1) # vertical, horizontal, index plt.imshow(stretch(hbt.remove_sfit(im, degree=5))) plt.title('remove_sfit(image_raw, degree=5), mean=' + hbt.trunc(np.mean(im),3)) plt.colorbar() # Column 2: Stray only. This will throw an error if we haven't read in a stray light file -- just ignore it. plt.subplot(1,3,2) try: plt.imshow(stretch(hbt.remove_sfit(image_stray, degree=5))) # This won't do much since it is already applied except UnboundLocalError: print("No stray light to subtract") plt.title('remove_sfit(stray_norm, degree=5), mean=' + hbt.trunc(np.mean(im),3)) # Column 3: raw - stray plt.subplot(1,3,3) try: im = hbt.remove_sfit(image_raw - image_stray,degree=5) plt.imshow(stretch(im)) except UnboundLocalError: print("No stray light to subtract") plt.title('remove_sfit(image_raw - image_stray, degree=5), med ' + hbt.trunc(np.median(im),3)) plt.show() # Now return the array. If we have a mask, then we return it too, as a tuple if (DO_MASK): # If we loaded a mask return (image_processed, mask) else: return image_processed
plt.subplot(1,4,1) plt.imshow(stretch(image_raw)) plt.title('stretch(image_raw)') plt.subplot(1,4,2) plt.imshow(stretch(im)) plt.imshow(mask_objects, alpha=0.2, cmap='plasma') plt.title('stretch(image_raw), overlay w mask_objects') plt.subplot(1,4,3) plt.imshow(mask_objects) plt.title('mask_objects') plt.show() plt.subplot(1,4,1) plt.imshow(stretch(hbt.remove_sfit(image_raw,degree=5)), cmap='plasma') plt.title('hbt.remove_sfit(image_raw)') plt.subplot(1,4,3) plt.imshow(stretch(hbt.remove_sfit(image_raw,degree=5, mask=np.logical_not(mask_objects))), cmap='plasma') plt.title('hbt.remove_sfit(mask=mask_objects)') plt.show() ### # method = 'String' # index_group = 5 # index_image = 2 # file = '/Users/throop/data/NH_Jring/data/jupiter/level2/lor/all/lor_0034676524_0x630_sci_1_opnav.fit' # image_raw = hbt.read_lorri(file)