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_LR_mosaic(left_path, right_path, left_samps, mosaic_path, lines, samps): isis.handmos( left_path, mosaic=mosaic_path, create="YES", nlines=lines, nsamp=samps, nbands=1, outsamp=1, outline=1, outband=1, ) isis.handmos( right_path, mosaic=mosaic_path, outsamp=(left_samps + 1), outline=1, outband=1, ) return
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 handmos_side(cubes, base_cube, out_p: os.PathLike, left=True): """Runs handmos to add cubes which are to one side or the other of the *base_cube*.""" # handmos left side side = 1 priority = "beneath" if left: side = -1 priority = "ontop" ssm = 1 slm = 1 for c in cubes[(cubes.index(base_cube) + side)::side]: slm -= side * round(c.line_offset) ssm -= side * round(c.samp_offset) isis.handmos( c.next_path, mosaic=out_p, priority=priority, outline=slm, outsample=ssm, outband=1, ) return
def main(): parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "-o", "--output", required=False, default=".mdr.iof.cub" ) parser.add_argument("-e", "--edr", required=True) parser.add_argument( "-c", "--conf", required=False, type=argparse.FileType('r'), default=pkg_resources.resource_stream( __name__, 'data/hical.pipelines.conf' ), ) parser.add_argument("mdr", metavar="MDR_file") parser.add_argument( "-l", "--log", required=False, default="WARNING", help="The log level to show for this program, can " "be a named log level or a numerical level.", ) parser.add_argument( "-k", "--keep", required=False, default=False, action="store_true", help="Normally, the program will clean up any " "intermediary files, but if this option is given, it " "won't.", ) args = parser.parse_args() util.set_logger(args.verbose, args.logfile, args.log) edr_path = Path(args.edr) mdr_path = Path(args.mdr) to_del = isis.PathSet() h2i_path = to_del.add(edr_path.with_suffix(".hi2isis.cub")) out_path = util.path_w_suffix(args.output, edr_path) # The first thing Alan's program did was to crop the image down to only the # 'imaging' parts. We're not doing that so the resultant file has a # geometry similar to what comes out of ISIS hical. # Convert the EDR to a cube file isis.hi2isis(edr_path, to=h2i_path) # Convert Alan's MDR to a cube file mdr_cub_path = to_del.add(mdr_path.with_suffix(".alan.cub")) logger.info(f"Running gdal_translate {mdr_path} -of ISIS3 {mdr_cub_path}") gdal.Translate(str(mdr_cub_path), str(mdr_path), format="ISIS3") h2i_s = int(isis.getkey_k(h2i_path, "Dimensions", "Samples")) h2i_l = int(isis.getkey_k(h2i_path, "Dimensions", "Lines")) mdr_s = int(isis.getkey_k(mdr_cub_path, "Dimensions", "Samples")) mdr_l = int(isis.getkey_k(mdr_cub_path, "Dimensions", "Lines")) if h2i_s != mdr_s: label = pvl.load(str(h2i_path)) hirise_cal_info = get_one( label, "Table", "HiRISE Calibration Ancillary" ) buffer_pixels = get_one(hirise_cal_info, "Field", "BufferPixels")[ "Size" ] dark_pixels = get_one(hirise_cal_info, "Field", "DarkPixels")["Size"] rev_mask_tdi_lines = hirise_cal_info["Records"] if h2i_s + buffer_pixels + dark_pixels == mdr_s: logger.info( f"The file {mdr_cub_path} has " f"{buffer_pixels + dark_pixels} more sample pixels " f"than {h2i_path}, assuming those are dark and " "buffer pixels and will crop accordingly." ) if h2i_l + rev_mask_tdi_lines != mdr_l: logger.critical( 'Even assuming this is a "full" channel ' "image, this has the wrong number of lines. " f"{mdr_cub_path} should have " f"{h2i_l + rev_mask_tdi_lines}, but " f"has {mdr_l} lines. Exiting" ) sys.exit() else: crop_path = to_del.add(mdr_cub_path.with_suffix(".crop.cub")) # We want to start with the next pixel (+1) after the cal # pixels. isis.crop( mdr_cub_path, to=crop_path, sample=buffer_pixels + 1, nsamples=h2i_s, line=rev_mask_tdi_lines + 1, ) mdr_cub_path = crop_path mdr_l = int(isis.getkey_k(mdr_cub_path, "Dimensions", "Lines")) else: logger.critical( f"The number of samples in {h2i_path} ({h2i_s}) " f"and {mdr_cub_path} ({mdr_s}) are different. " "Exiting." ) sys.exit() if h2i_l != mdr_l: logger.critical( f"The number of lines in {h2i_path} ({h2i_l}) " f"and {mdr_cub_path} ({mdr_l}) are different. " "Exiting." ) sys.exit() # Convert the EDR to the right bit type for post-HiCal Pipeline: h2i_16b_p = to_del.add(h2i_path.with_suffix(".16bit.cub")) isis.bit2bit( h2i_path, to=h2i_16b_p, bit="16bit", clip="minmax", minval=0, maxval=1.5, ) shutil.copyfile(h2i_16b_p, out_path) # If it is a channel 1 file, Alan mirrored it so that he could process # the two channels in an identical way (which we also took advantage # of above if the buffer and dark pixels were included), so we need to # mirror it back. cid = hirise.get_ChannelID_fromfile(h2i_16b_p) if cid.channel == "1": mirror_path = to_del.add(mdr_cub_path.with_suffix(".mirror.cub")) isis.mirror(mdr_cub_path, to=mirror_path) mdr_cub_path = mirror_path # Is the MDR in DN or I/F? maximum_pxl = float( pvl.loads(isis.stats(mdr_cub_path).stdout)["Results"]["Maximum"] ) if maximum_pxl < 1.5: logger.info("MDR is already in I/F units.") mdr_16b_p = to_del.add(mdr_cub_path.with_suffix(".16bit.cub")) isis.bit2bit( mdr_cub_path, to=mdr_16b_p, bit="16bit", clip="minmax", minval=0, maxval=1.5, ) isis.handmos(mdr_16b_p, mosaic=out_path) else: logger.info("MDR is in DN units and will be converted to I/F.") fpa_t = statistics.mean( [ float( isis.getkey_k( h2i_16b_p, "Instrument", "FpaPositiveYTemperature" ) ), float( isis.getkey_k( h2i_16b_p, "Instrument", "FpaNegativeYTemperature" ) ), ] ) print(f"fpa_t {fpa_t}") conf = pvl.load(args.conf) tdg = t_dep_gain(get_one(conf["Hical"], "Profile", cid.ccdname), fpa_t) suncorr = solar_correction() sclk = isis.getkey_k( h2i_16b_p, "Instrument", "SpacecraftClockStartCount" ) target = isis.getkey_k(h2i_16b_p, "Instrument", "TargetName") suncorr = solar_correction(sunDistanceAU(sclk, target)) sed = float( isis.getkey_k(h2i_16b_p, "Instrument", "LineExposureDuration") ) zbin = get_one(conf["Hical"], "Profile", "GainUnitConversion")[ "GainUnitConversionBinFactor" ] # The 'ziof' name is from the ISIS HiCal/GainUnitConversion.h, it is a # divisor in the calibration equation. print(f"zbin {zbin}") print(f"tdg {tdg}") print(f"sed {sed}") print(f"suncorr {suncorr}") ziof = zbin * tdg * sed * 1e-6 * suncorr eqn = f"\(F1 / {ziof})" # noqa W605 mdriof_p = to_del.add(mdr_cub_path.with_suffix(".iof.cub")) to_s = "{}+SignedWord+{}:{}".format(mdriof_p, 0, 1.5) isis.fx(f1=mdr_cub_path, to=to_s, equ=eqn) isis.handmos(mdriof_p, mosaic=out_path) if not args.keep: to_del.unlink()
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 match_red(cubes: list, base_cube, flat_path, elargement_ratio=1.0006): """Prepare cubs for hijitreg by matching size and binning to center red ccds. """ # Where does the 1.0006 value for OPTICAL_ENLARGEMENT_RATIO come from? # Not sure. red4 = list(filter(lambda x: int(x.ccdnumber) == 4, cubes))[0] red5 = list(filter(lambda x: int(x.ccdnumber) == 5, cubes))[0] if red4.bin != red5.bin: raise ValueError("RED4 and RED5 binning not equal.") for c in cubes: bin_ratio = c.bin / base_cube.bin # Differences in field of view between color and red are corrected # for by multiplying bin ratio by pre-determined constant for BG and # dividing bin ratio by that constant for IR mag = bin_ratio if c.ccdname == "BG": mag = bin_ratio * elargement_ratio elif c.ccdname == "IR": mag = bin_ratio / elargement_ratio # scale cubes as needed if mag > 1: isis.enlarge( c.path, to=c.next_path, sscale=mag, lscale=bin_ratio, interp="BILINEAR", ) elif mag < 1: isis.reduce( c.path, to=c.next_path, sscale=1 / mag, lscale=bin_ratio, mode="SCALE", ) else: shutil.copy(c.path, c.next_path) if c.ccdname != "RED": offset = int( (200 * (c.bin - base_cube.bin) + c.tdi - base_cube.tdi) / base_cube.bin ) mos_path = c.next_path.with_suffix(".mosaiced.cub") isis.handmos( c.next_path, mosaic=mos_path, create="Y", nlines=base_cube.lines, nsamples=base_cube.samps, nbands=1, outline=offset, outsamp=1, outband=1, ) shutil.move(mos_path, c.next_path) if bin_ratio != 1: isis.editlab( c.next_path, options="MODKEY", grpname="INSTRUMENT", KEYWORD="SUMMING", value=base_cube.bin, ) # This section creates flat.tabs for RED4-RED5 pair only rows = int(red5.lines / 50) isis.hijitreg( red4.next_path, match=red5.next_path, row=rows, flat=flat_path ) return
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
def process_set( red: HiColorCube, ir: HiColorCube, bg: HiColorCube, outsuffix: str, keep=False, ): """Do all of the scaling and cropping for a RED/IR/BG set.""" if (len( set( map( lambda c: c.get_obsid(), filter(lambda x: x is not None, [red, ir, bg]), ))) != 1): raise ValueError("These cube files don't all have the same " f"Observation ID: {red}, {ir}, {bg}") temp_token = datetime.now().strftime("HiColorInit-%y%m%d%H%M%S") # These two values are calculated but only written to a PVL file, # which I think we can skip. # # in bin1 there are 48 pixels of overlap # total_width = 2 * red.samps - (48 / red.bin) # # # in bin1, the right half starts at pixel 2001 # image_midpoint = 2000 / red.bin + 1 for c in filter(lambda x: x is not None, [ir, bg]): # Calculate delta offset in lines between red and color ccd offset = int((200 * (c.bin - red.bin) + c.tdi - red.tdi) / red.bin) bin_ratio = c.bin / red.bin # tdi_ratio = c.tdi / red.tdi mag_ratio = bin_ratio / 1.0006 # ratio of color to red for enlargement, correction of optical # distortion from OPTICAL_ENLARGEMENT_RATIO constant in original # HiColor.pm # Rescale the color by the bin ratio and mag ratio, to match the red. # These will be the BG and IR "pre-color" cubes. rescaled = c.path.with_suffix(f".{temp_token}.rescaled" + outsuffix) if mag_ratio < 1: s = 1 / mag_ratio isis.reduce( c.path, to=rescaled, sscale=s, lscale=bin_ratio, validper=1, algorithm="nearest", vper_replace="nearest", ) else: isis.enlarge( c.path, to=rescaled, sscale=mag_ratio, lscale=bin_ratio, interp="bilinear", ) # The original Perl had an additional step to divide c.bin by the # bin_ratio, and provide that to value= below, but that's # mathematically just red.bin, so we'll skip a calculation: isis.editlab( rescaled, options="modkey", grpname="Instrument", keyword="Summing", value=red.bin, ) # trim by placing in a new image isis.handmos( rescaled, mosaic=c.path.with_suffix(outsuffix), create="Y", nlines=red.lines, nsamp=red.samps, nband=1, outline=offset, outsamp=1, outband=1, ) if not keep: rescaled.unlink() logger.info(f"Created {c.path.with_suffix(outsuffix)}") return