def make_unfiltered(in_path, nrm_path, temp_token, color_code, color_band, keep=False): tt = f"{temp_token}_{color_code}" in_p = Path(in_path) unfiltered_p = in_p.with_name( in_p.name.replace("COLOR", "UNFILTERED_COLOR")) shutil.copyfile(in_p, unfiltered_p) # run ISIS algebra program to create unfiltered # (normalized-IR/RED)*RED files alg_unfiltered_path = in_p.with_suffix(f".{tt}.algebra.cub") isis.algebra( nrm_path, from2="{}+2".format(in_p), to=alg_unfiltered_path, operator="MULTIPLY", ) # Update the output file with the non-filtered normalized IR and BG bands isis.handmos( alg_unfiltered_path, mosaic=unfiltered_p, outsample=1, outline=1, matchbandbin=False, outband=color_band, ) if not keep: alg_unfiltered_path.unlink() return unfiltered_p
def make_balance(cube, conf, balance_path): a_param = None c_param = None if conf["HiccdStitch_Balance_Correction"] == "MULTIPLY": a_param = cube.correction c_param = 0 elif conf["HiccdStitch_Balance_Correction"] == "ADD": a_param = 1 c_param = cube.correction to_s = "{}+SignedWord+{}:{}".format( balance_path, conf["HiccdStitch_Normalization_Minimum"], conf["HiccdStitch_Normalization_Maximum"], ) isis.algebra(cube.nextpath, to=to_s, operator="unary", a=a_param, c=c_param) logger.info(f"Created {balance_path}")
def subtract_over_thresh( in_path: Path, out_path: Path, thresh: int, delta: int, keep=False ): """This is a convenience function that runs ISIS programs to add or subtract a value to DN values for pixels that are above or below a threshold. For all pixels in the *in_path* ISIS cube, if *delta* is positive, then pixels with a value greater than *thresh* will have *delta* subtracted from them. If *delta* is negative, then all pixels less than *thresh* will have *delta* added to them. """ # Originally, I wanted to just do this simply with fx: # eqn = "\(f1 + ((f1{glt}={thresh}) * {(-1 * delta)}))" # However, fx writes out floating point pixel values, and we really # need to keep DNs as ints as long as possible. Sigh. shutil.copyfile(in_path, out_path) mask_p = in_path.with_suffix(".threshmask.cub") mask_args = {"from": in_path, "to": mask_p} if delta > 0: mask_args["min"] = thresh else: mask_args["max"] = thresh isis.mask(**mask_args) delta_p = in_path.with_suffix(".delta.cub") isis.algebra( mask_p, from2=in_path, to=delta_p, op="add", a=0, c=(-1 * delta) ) isis.handmos(delta_p, mosaic=out_path) if not keep: mask_p.unlink() delta_p.unlink() return
def HiFurrow_Fix(in_cube: os.PathLike, out_cube: os.PathLike, max_mean: float, keep=False): """Perform a normalization of the furrow region of bin 2 or 4 HiRISE images. The input to this script is a HiRISE stitch product containing both channels of a CCD. """ in_cub = Path(in_cube) binning = int(isis.getkey_k(in_cub, "Instrument", "Summing")) lines = int(isis.getkey_k(in_cub, "Dimensions", "Lines")) samps = int(isis.getkey_k(in_cub, "Dimensions", "Samples")) if binning != 2 and binning != 4: raise ValueError("HiFurrow_Fix only supports correction for " "bin 2 or 4 data.") if binning == 2 and samps != 1024: raise ValueError(f"HiFurrowFix: improper number of samples: {samps}, " "for a stitch product with bin 2 (should be 1024).") # This string will get placed in the filename for all of our # temporary files. It will (hopefully) prevent collisions with # existing files and also allow for easy clean-up if keep=True temp_token = datetime.now().strftime("HFF-%y%m%d%H%M%S") to_del = isis.PathSet() # For bin2 and bin4 imaging, specify width of furrow based on # image average DN range range_low = {2: (512, 513), 4: (256, 257)} # 2 pixel furrow width range_mid = {2: (511, 514), 4: (255, 258)} # 4 pixel furrow width range_hgh = {2: (511, 514), 4: (255, 258)} # 4 pixel furrow width range_max = {2: (510, 515), 4: (254, 259)} # 6 pixel furrow width # Original code had low/mid/hgh for bin2 and bin4, but they # were hard-coded to be identical. dn_range_low = 9000 dn_range_mid = 10000 dn_range_hgh = 12000 if max_mean > dn_range_hgh: dn_range = range_max[binning] elif max_mean > dn_range_mid: dn_range = range_hgh[binning] elif max_mean > dn_range_low: dn_range = range_mid[binning] else: dn_range = range_low[binning] lpf_samp = int((dn_range[1] - dn_range[0] + 1) / 2) * 4 + 1 lpf_line = int(lpf_samp / 2) * 20 + 1 # Create a mask file # DN=1 for non-furrow area # DN=0 for furrow area eqn = rf"\(1*(sample<{dn_range[0]})+ 1*(sample>{dn_range[1]}) + 0)" fx_cub = to_del.add(in_cub.with_suffix(f".{temp_token}.fx.cub")) isis.fx(to=fx_cub, mode="OUTPUTONLY", lines=lines, samples=samps, equation=eqn) # Create a file where the furrow area is set to null mask1_cub = to_del.add(in_cub.with_suffix(f".{temp_token}.mask1.cub")) isis.mask( in_cub, mask=fx_cub, to=mask1_cub, min_=1, max_=1, preserve="INSIDE", spixels="NULL", ) # Lowpass filter to fill in the null pixel area lpf_cub = to_del.add(in_cub.with_suffix(f".{temp_token}.lpf.cub")) isis.lowpass( mask1_cub, to=lpf_cub, sample=lpf_samp, line=lpf_line, null=True, hrs=False, his=False, lis=False, ) # Create a file where non-furrow columns are set to null mask2_cub = to_del.add(in_cub.with_suffix(f".{temp_token}.mask2.cub")) isis.mask( in_cub, mask=fx_cub, to=mask2_cub, min_=0, max_=0, preserve="INSIDE", spixels="NULL", ) # Highpass filter the furrow region hpf_cub = to_del.add(in_cub.with_suffix(f".{temp_token}.hpf.cub")) isis.highpass(mask2_cub, to=hpf_cub, sample=1, line=lpf_line) # Add lowpass and highpass together to achieve desired result alg_cub = to_del.add(in_cub.with_suffix(f".{temp_token}.alg.cub")) isis.algebra(from_=lpf_cub, from2=hpf_cub, to=alg_cub, operator="ADD", A=1.0, B=1.0) # copy the input file to the output file then mosaic the # furrow area as needed. logger.info(f"Copy {in_cub} to {out_cube}.") shutil.copyfile(in_cub, out_cube) isis.handmos( alg_cub, mosaic=out_cube, outsample=1, outline=1, outband=1, insample=1, inline=1, inband=1, create="NO", ) if not keep: to_del.unlink() return
def per_band(cubes, out_p, temp_token, color_code, furrow_flag, unfiltered, keep=False) -> float: to_del = isis.PathSet() # Generate the handmos of both the ratio and the croped ratio files if len(cubes) == 2: maxlines = max(c.lines for c in cubes) maxsamps = sum(c.samps for c in cubes) # Generate the mosaic of the ratio image mos_p = to_del.add( out_p.with_suffix(f".{temp_token}.{color_code}.mos.cub")) make_LR_mosaic( cubes[0].mask_path[color_code], cubes[1].mask_path[color_code], cubes[0].samps, mos_p, maxlines, maxsamps, ) # Generate the mosaic of the crop ratio image maxlines = max(c.crop_lines for c in cubes) crpmos_p = to_del.add( out_p.with_suffix(f".{temp_token}.{color_code}.moscrop.cub")) make_LR_mosaic( cubes[0].crop_path[color_code], cubes[1].crop_path[color_code], cubes[0].samps, crpmos_p, maxlines, maxsamps, ) else: mos_p = cubes[0].mask_path[color_code] crpmos_p = cubes[0].crop_path[color_code] mosnrm_p = to_del.add( out_p.with_suffix(f".{temp_token}.{color_code}.mosnorm.cub")) ratio_stddev = cubenorm_stats(crpmos_p, mos_p, mosnrm_p, keep=keep) # from the handmos cubes, pull out the individual CCDs with crop if len(cubes) == 2: samp = 1 for i, c in enumerate(cubes): cubes[i].nrm_path[color_code] = c.path.with_suffix( f".{temp_token}.{color_code}.norm.cub") isis.crop( mosnrm_p, to=cubes[i].nrm_path[color_code], sample=samp, nsamples=c.samps, ) samp += c.samps else: # If there was only one file then all we need to do is rename files cubes[0].nrm_path[color_code] = mosnrm_p if unfiltered: # Create the unfiltered color cubes, if needed for c in cubes: make_unfiltered( c.path, c.nrm_path[color_code], temp_token, color_code, c.band[color_code], keep=keep, ) # low pass filter the files for c in cubes: # If $lpfz=1 then we're only interpolating null pixels caused by # noise or furrow removal lowpass_args = dict( minopt="PERCENT", minimum=25, low=0.00, high=2.0, null=True, hrs=True, lis=True, lrs=True, ) # As the comment below states, I *think* that the OP had a copy # and paste error here, because even though it kept track of both # the IR and BG boxcar dimensions, they were always identical. logger.warning("The original Perl calculates the boxcar size for " "both the IR and BG based only on the ratio of the " "IR to the RED.") if c.get_boxcar_size(c.ir_bin) == 1: lowpass_args["lines"] = 3 lowpass_args["samples"] = 3 lowpass_args["filter"] = "OUTSIDE" else: lowpass_args["lines"] = 1 lowpass_args["samples"] = 1 lpf_path = to_del.add( c.path.with_suffix(f".{temp_token}.{color_code}.lpf.cub")) isis.lowpass(c.nrm_path[color_code], to=lpf_path, **lowpass_args) # Perform lpfz filters to interpolate null pixels due to furrows or # bad pixels if furrow_flag: lpfz_path = c.path.with_suffix( f".{temp_token}.{color_code}.lpfz.cub") lpfz_triplefilter(lpf_path, lpfz_path, temp_token, keep=keep) else: lpfz_path = lpf_path # run ISIS algebra program to created (normalized-IR/RED)*RED files alg_path = to_del.add( c.path.with_suffix(f".{temp_token}.{color_code}.algebra.cub")) isis.algebra(lpfz_path, from2=f"{c.path}+2", to=alg_path, operator="MULTIPLY") # Update the output file with the normalized IR and BG bands isis.handmos( alg_path, mosaic=c.final_path, outsample=1, outline=1, outband=c.band[color_code], matchbandbin=False, ) if not keep: to_del.unlink() return ratio_stddev
def HiBeautify(cube_paths: list, conf: dict, out_irb="_IRB.cub", out_rgb="_RGB.cub", keep=False): logger.info(f"HiBeautify start: {', '.join(map(str, cube_paths))}") # GetConfigurationParameters() cubes = list(map(hcn.ColorCube, cube_paths)) cubes.sort() irb_out_p = hcn.set_outpath(out_irb, cubes) rgb_out_p = hcn.set_outpath(out_rgb, cubes) temp_token = datetime.now().strftime("HiBeautify-%y%m%d%H%M%S") to_del = isis.PathSet() # Create an IRB mosaic from the HiColorNorm halves. # If a half is missing, we create a mosaic with the proper width and place # the half in it at the proper location. # Logic ofr image_midpoint and total_width come from original HiColorInit # irbmerged_p = to_del.add(out_p.with_suffix(f'.{temp_token}_IRB.cub')) image_midpoint = int((2000 / cubes[0].red_bin) + 1) outsample = image_midpoint if cubes[0].ccdnumber == "4": outsample = 1 total_width = int(2 * cubes[0].samps - (48 / cubes[0].red_bin)) isis.handmos( cubes[0].path, mosaic=irb_out_p, outline=1, outsample=outsample, outband=1, create="Y", nlines=cubes[0].lines, nsamp=total_width, nbands=3, ) if len(cubes) == 1: logger.info("Warning, missing one half!") else: logger.info("Using both halves") isis.handmos( cubes[1].path, mosaic=irb_out_p, outline=1, outsample=image_midpoint, outband=1, ) # Nothing is actually done to the pixels here regarding the FrostStats, so # I'm just going to skip them here. # # # Determine if Frost/ICE may be present using FrostStats module. # frost = None # if args.frost: # frost = True # logging.info('Frost override: disabling auto-detection and using ' # 'the frost/ice color stretch') # if args.nofrost: # frost = False # logging.info('Frost override: disable auto-detection and not using ' # 'the frost/ice color stretch') # if frost is None: # pass # # get frost stats # Subtract the unaltered RED band from the high pass filtered BG for # synthetic blue. logger.info("Creating synthetic B, subtracting RED from BG") rgbsynthb_p = to_del.add(irb_out_p.with_suffix(f".{temp_token}_B.cub")) isis.algebra( f"{irb_out_p}+3", from2=f"{irb_out_p}+2", op="subtract", to=rgbsynthb_p, A=conf["Beautify"]["Synthetic_A_Coefficient"], B=conf["Beautify"]["Synthetic_B_Coefficient"], ) # Adjust the BandBin group isis.editlab(rgbsynthb_p, grpname="BandBin", keyword="Name", value="Synthetic Blue") isis.editlab(rgbsynthb_p, grpname="BandBin", keyword="Center", value="0") isis.editlab(rgbsynthb_p, grpname="BandBin", keyword="Width", value="0") # HiBeautify gathers and writes a bunch of statistics to PVL that is # important to the HiRISE GDS, but not relevant to just producing pixels # so I'm omitting it. # # # Determine the min and max DN values of each band (RED, IR, BG, B) we're # # working with. # (upper, lower) = conf['Beautify']['Stretch_Exclude_Lines'] # if upper == 0 and lower == 0: # synthbcrp_p = rgbsynthb_p # irbmrgcrp_p = irbmerged_p # else: # synthbcrp_p = to_del.add( # out_p.with_suffix(f'.{temp_token}_Bx.cub')) # irbmrgcrp_p = to_del.add( # out_p.with_suffix(f'.{temp_token}_IRBx.cub')) # # for (f, t) in ((rgbsynthb_p, synthbcrp_p), # (irbmerged_p, irbmrgcrp_p)): # logging.info(isis.crop(f, to=t, propspice=False, # line=(1 + upper), # nlines=( # cubes[0].lines - lower + upper)).args) # # stats = dict() # stats['B'] = Get_MinMax(synthbcrp_p, # conf['Beautify']['Stretch_Reduction_Factor'], # temp_token, keep=keep) # # for band in cubes[0].band.keys(): # stats[band] = Get_MinMax('{}+{}'.format(str(irbmrgcrp_p), # cubes[0].band[band]), # conf['Beautify']['Stretch_Reduction_Factor'], # temp_token, keep=keep) # Create an RGB cube using the RED from the IRB mosaic, # the BG from the IRB mosaic and the synthetic B that we just made. isis.cubeit_k([f"{irb_out_p}+2", f"{irb_out_p}+3", rgbsynthb_p], to=rgb_out_p) if not keep: to_del.unlink() return