diff --git a/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.py b/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.py index b4fb65fb525a128dda0f17d011945eea323a61a2..f74147d490fc087f0e75ffea5b83eccdb632c8a7 100755 --- a/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.py +++ b/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.py @@ -67,14 +67,21 @@ def read_cubelist(cube_list : Path): list list of files """ + files = [] + with open(cube_list) as c: content = c.read() content = content.strip() files = content.split("\n") - return files + + for i in range(len(files)): + files[i] = os.path.abspath(files[i]) + return files + -def segment(img_path : Path, nlines : int = MAX_LEN): + +def segment(img_path : Path, workdir : Path, nlines : int = MAX_LEN): """ Segments an image into multiple parts. @@ -97,19 +104,28 @@ def segment(img_path : Path, nlines : int = MAX_LEN): segment_metadata = {} try: - ret = kisis.segment(img_path, nl=nlines, overlap=0, pref__="$ISISROOT/IsisPreferences") + # change workdir so output goes there + oldpwd = os.getcwd() + + # create if it doesn't exist + workdir.mkdir(parents=True, exist_ok=True) + work_img = workdir / img_path.name + shutil.copyfile(img_path, work_img) + ret = kisis.segment(work_img, nl=nlines, overlap=0, pref__="$ISISROOT/IsisPreferences") + os.remove(work_img) + log.debug(f"{ret}") segment_metadata = pvl.loads(filter_progress(ret.stdout)) # comes nested in a "results" group, trim it off segment_metadata = [s[1] for s in segment_metadata] - glob_str = str(img_path.parent / img_path.stem) + ".segment*" + glob_str = str(workdir / img_path.stem) + ".segment*" log.debug(f"globbing segments: glob({glob_str})") segments = sorted(glob(glob_str)) - + log.debug(f"segments: {segments}") - + i = 0 for s, meta in zip(segments, segment_metadata): i += 1 @@ -133,19 +149,27 @@ def segment(img_path : Path, nlines : int = MAX_LEN): def generate_cnet(params, images): - - match_segment_n = images["match"]["Segment"] + if isinstance(images["match"], list): + images_match_n = images["match"][0] + else: + images_match_n = images["match"] + match_segment_n = images_match_n["Segment"] from_segment_n = images["from"][0]["Segment"] + print(images) + workdir = Path(params["WORKDIR"]) + new_params = deepcopy(params) new_params.pop("NL") new_params.pop("MINAREA") new_params.pop("MINTHICKNESS") - + new_params.pop("WORKDIR") + + # make sure none of these keys are still in the params new_params.pop("FROMLIST", None) new_params.pop("FROM", None) - new_params["MATCH"] = images["match"]["Path"] + new_params["MATCH"] = images_match_n["Path"] og_onet = Path(params["ONET"]) og_tolist = Path(params["TOLIST"]) @@ -158,7 +182,7 @@ def generate_cnet(params, images): match_stem = Path(new_params["MATCH"]).stem - fromlist_path = Path(og_tolist.parent / f"from_images_segment{from_segment_n}.lis") + fromlist_path = Path(workdir / f"from_images_segment{from_segment_n}.lis") from_stem = fromlist_path.stem if "debuglog" in new_params: @@ -168,49 +192,40 @@ def generate_cnet(params, images): new_params["NETWORKID"] = og_networkid + f"_{match_segment_n}_{from_stem}" new_params["POINTID"] = og_pointid + f"_{match_segment_n}_{from_stem}" - new_params["ONET"] = f"{og_onet.parent/og_onet.stem}_{match_segment_n}_{from_stem}.net" - new_params["TOLIST"] = f"{og_tolist.parent/og_tolist.stem}_{match_segment_n}_{from_stem}.lis" - + new_params["ONET"] = f"{og_onet.stem}_{match_segment_n}_{from_stem}.net" + new_params["TOLIST"] = f"{og_tolist.stem}_{match_segment_n}_{from_stem}.lis" + log.debug(new_params) + overlapfromlist = workdir / f"{og_tolist.stem}_{match_segment_n}_{from_stem}_overlap_fromlist.lis" + overlaptolist = workdir / f"{og_tolist.stem}_{match_segment_n}_{from_stem}.overlaps" + # check for overlaps is_overlapping = [] - for image in from_images: - with tempfile.TemporaryDirectory() as tmpdir: - tmpdir = Path(tmpdir) - overlapfromlist = tmpdir / "fromlist.lis" - overlaptolist = tmpdir / "tolist.lis" - - kisis.fromlist.make([*from_images, new_params["MATCH"]], overlapfromlist) - - try: - kisis.findimageoverlaps(fromlist=overlapfromlist, overlaplist=overlaptolist) - except subprocess.CalledProcessError as err: - print('Had an ISIS error:') - print(' '.join(err.cmd)) - print(err.stdout) - print(err.stderr) - raise err - - ret = kisis.overlapstats(fromlist=overlapfromlist, overlaplist=overlaptolist, pref__="$ISISROOT/IsisPreferences") - stats = pvl.loads(filter_progress(ret.stdout)) - log.debug(f"overlap stats: {ret.stdout}") - - # first, throw it out if there is no overlap whatsoever - is_pair_overlapping = not any([k[1].get("NoOverlap", "") == new_params["MATCH"] for k in stats]) - - if is_pair_overlapping: - is_thick_enough = stats["Results"]["ThicknessMinimum"] > float(params.get("MINTHICKNESS", 0)) - is_area_large_enough = stats["Results"]["AreaMinimum"] > float(params.get("MINAREA", 0)) - is_pair_overlapping = all([is_thick_enough, is_area_large_enough]) - is_overlapping.append(is_pair_overlapping) - else: # not overlapping - is_overlapping.append(False) - - # mask images - from_images = list(compress(from_images, is_overlapping)) - log.debug(f"From images overlapping Match: {from_images}") + all_images = [*from_images, new_params["MATCH"]] + print("ALL IMAGES: ", " ".join(all_images)) + kisis.fromlist.make(all_images, overlapfromlist) + try: + kisis.findimageoverlaps(fromlist=overlapfromlist, overlaplist=overlaptolist) + except subprocess.CalledProcessError as err: + print('Find Image Overlaps Had an ISIS error:') + print(' '.join(err.cmd)) + print(err.stdout) + print(err.stderr) + return "No Overlaps From FindImageOverlaps" + + ret = kisis.overlapstats(fromlist=overlapfromlist, overlaplist=overlaptolist, pref__="$ISISROOT/IsisPreferences") + stats = pvl.loads(filter_progress(ret.stdout)) + log.debug(f"overlap stats: {ret.stdout}") + + # # first, throw it out if there is no overlap whatsoever + # has_overlap = not any([len(k[1].get("NoOverlap", "")) == new_params["MATCH"] for k in stats]) + + # # mask images + # from_images = list(compress(from_images, is_overlapping)) + log.debug(f"From images overlapping Match: {from_images}") + log.debug(f"Has overlaps list: {is_overlapping}") if from_images: log.debug(f"FROMLIST: {from_images}") @@ -221,32 +236,35 @@ def generate_cnet(params, images): else: log.debug(f"{fromlist_path} already exists") new_params["FROMLIST"] = str(fromlist_path) + + log.debug(f"Running FindFeatures with Params: {new_params}") try: ret = kisis.findfeatures(**new_params) log.debug(f"returned: {ret}") except subprocess.CalledProcessError as err: - log.debug('Had an ISIS error:') + log.debug('Find Features Had an ISIS error:') log.debug(' '.join(err.cmd)) log.debug(err.stdout) log.debug(err.stderr) - return "ERROR" - segmented_net = cnet.from_isis(new_params["ONET"]) + if os.path.exists(new_params["ONET"]): + segmented_net = cnet.from_isis(new_params["ONET"]) - # starting sample in inclusive, so subtract 1 - segmented_net.loc[segmented_net.serialnumber == images["match"]["SN"], "line"] += images["match"]["StartingLine"]-1 + # starting sample in inclusive, so subtract 1 + segmented_net.loc[segmented_net.serialnumber == images_match_n["SN"], "line"] += images_match_n["StartingLine"]-1 - # offset the images - for k, image in enumerate(images["from"]): - image_sn = image["SN"] + # offset the images + for k, image in enumerate(images["from"]): + image_sn = image["SN"] - # starting sample is inclusive, so subtract 1 - segmented_net.loc[segmented_net.serialnumber == image_sn, "line"] += starting_lines[k]-1 - cnet.to_isis(segmented_net, new_params["ONET"], targetname="moon") - - from_originals = [image["Original"] for image in images["from"]] - return {"onet": new_params["ONET"], "original_images": from_originals} + # starting sample is inclusive, so subtract 1 + segmented_net.loc[segmented_net.serialnumber == image_sn, "line"] += starting_lines[k]-1 + cnet.to_isis(segmented_net, new_params["ONET"], targetname="moon") + + from_originals = [image["Original"] for image in images["from"]] + return {"onet": new_params["ONET"], "original_images": from_originals} + return "No Overlap" else: return "No Overlap" @@ -274,7 +292,7 @@ def merge(d1, d2, k): return v1+v2 -def findFeaturesSegment(ui): +def findFeaturesSegment(ui, workdir): """ findFeaturesSegment Calls FindFeatures on segmented images @@ -313,41 +331,69 @@ def findFeaturesSegment(ui): else: nthreads = int(multiprocessing.cpu_count()) - pool = ThreadPool(ceil(nthreads/len(img_list))) - output = pool.map_async(segment, img_list) + pool = ThreadPool(nthreads) + starmap_args = [] + for image in img_list: + starmap_args.append([image, workdir, ui.GetInteger("NL")]) + output = pool.starmap_async(segment, starmap_args) pool.close() pool.join() output = output.get() + match_segments = segment(os.path.abspath(ui.GetCubeName("match")), workdir, ui.GetInteger("NL")) - match_segments = segment(ui.GetCubeName("match"), ui.GetInteger("NL")) - from_segments = reduce(lambda d1,d2: {k: merge(d1, d2, k) for k in set(d1)|set(d2)}, output) + if len(img_list) > 1: + from_segments = reduce(lambda d1,d2: {k: merge(d1, d2, k) for k in set(d1)|set(d2)}, output) + else: + # img_list = 1 + from_segments = output[0] + for seg, value in from_segments.items(): + og_value = value + from_segments[seg] = [og_value] + segment_paths = [s["Path"] for sublist in list(from_segments.values()) for s in sublist] segment_paths = segment_paths + [s["Path"] for s in list(match_segments.values())] # re-generate footprints - pool = ThreadPool(ceil(nthreads/len(segment_paths))) + pool = ThreadPool(nthreads) output = pool.map_async(footprintinit, segment_paths) pool.close() pool.join() output = output.get() log.debug(f"{output}") - image_sets = list(itertools.product(match_segments.values(), from_segments.values())) - + # Remove redundant overlapping pairs + x = match_segments.values() + y = from_segments.values() + # x,y = (x,y) if len(x) > len(y) else (y,x) + image_cartesian = list(itertools.product(x, y)) + image_sets = image_cartesian + # for i in image_cartesian: + # if i[0][0]["Segment"] >= i[1]["Segment"]: + # image_sets.append(i) + # restructure things to be easier to manage job_dicts = [] for im in image_sets: match = im[0] from_images = im[1] + if not isinstance(from_images, list): + # from_images must be list type + from_images_list = [] + from_images_list.append(from_images) + from_images = from_images_list job_dicts.append({ "match" : match, "from" : from_images - }) - + }) + # get params as a dictionary params = ui.GetParams() + if is_workdir_temp: + params["WORKDIR"] = workdir - pool = ThreadPool(ceil(nthreads/len(job_dicts))) + # findfeatures is already threaded, limit python threads by the maxthreads + # parameter, no maththreads_findfeatures * maxthreads_python <= maxthreads + pool = ThreadPool(int(nthreads/len(job_dicts))) starmap_args = list(zip([params]*len(job_dicts), job_dicts)) output = pool.starmap_async(generate_cnet, starmap_args) pool.close() @@ -369,11 +415,11 @@ def findFeaturesSegment(ui): tolists = [set(o["original_images"]) for o in output if isinstance(o, dict)] final_images = set.union(*tolists) - final_images.add(ui.GetCubeName("match")) + final_images.add(os.path.abspath(ui.GetCubeName("match"))) log.debug(f"merged images: {final_images}") kisis.fromlist.make(final_images, Path(ui.GetFileName("tolist"))) - + if len(onets) > 1: try: kisis.cnetmerge(clist = onet_list, onet=ui.GetFileName("onet"), networkid=ui.GetAsString("networkid"), description=f"{ui.GetString('description')}") @@ -386,9 +432,23 @@ def findFeaturesSegment(ui): # Dont merge shutil.copy(onets[0], ui.GetFileName("onet")) - log.info(f"COMPLETE, wrote { ui.GetFileName("onet")}") - -if __name__ == "__main__": +if __name__ == "__main__": ui = astroset.init_application(sys.argv) - findFeaturesSegment(ui) - + is_workdir_temp = not ui.WasEntered("WorkDir") + workdir = Path(tempfile.TemporaryDirectory().name) + if ui.WasEntered("Workdir"): + workdir = Path(ui.GetFileName("Workdir")) + + try: + findFeaturesSegment(ui, workdir) + except Exception as e: + if is_workdir_temp: + shutil.rmtree(workdir) + raise e + + # log.info(f"COMPLETE, wrote: {ui.GetFileName("onet")}") + if is_workdir_temp: + shutil.rmtree(workdir) + log.info("Complete") + else: + log.info("Intermediate files written to %s", workdir) diff --git a/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.xml b/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.xml index 54ebc5f1cfd3f0d7e1ce14595f8c11bef295cee0..4cead8407d718a282075cce28e4f598a5a4b2973 100644 --- a/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.xml +++ b/isis/python_bindings/apps/findFeaturesSegment/findFeaturesSegment.xml @@ -139,6 +139,19 @@ <filter>*.lis</filter> </parameter> + <parameter name="WORKDIR"> + <type>filename</type> + <fileMode>output</fileMode> + <brief>Directory to output intermediate files to</brief> + <description> + This directory is where any intermediate and temp files are saved to. + If this is set to None (default), these files go into the temp directory + which is deleted when the program is terminated. Set this if you want to debug + a network. + </description> + <internalDefault>None</internalDefault> + </parameter> + <parameter name="NL"> <type>integer</type> <fileMode>output</fileMode> @@ -1432,4 +1445,4 @@ End </example> </examples> -</application> +</application> \ No newline at end of file