From 88d1bc743e0c84890f5c42c779f69951993a94b6 Mon Sep 17 00:00:00 2001 From: Rachel Pillsworth Date: Tue, 25 Nov 2025 09:16:20 -0800 Subject: [PATCH 1/3] Added ID matched skeleton map to save_fits command --- fil_finder/filfinder2D.py | 210 ++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 123 deletions(-) diff --git a/fil_finder/filfinder2D.py b/fil_finder/filfinder2D.py index d431684..0cf0ee7 100644 --- a/fil_finder/filfinder2D.py +++ b/fil_finder/filfinder2D.py @@ -14,6 +14,7 @@ import os import time import warnings +import concurrent.futures from .pixel_ident import recombine_skeletons, isolateregions from .utilities import eight_con, round_to_odd, threshold_local, in_ipynb @@ -68,12 +69,6 @@ class FilFinder2D(BaseInfoMixin): save_name : str, optional Sets the prefix name that is used for output files. Can be overridden in ``save_fits`` and ``save_table``. Default is "FilFinder_output". - pool : None, concurrent.futures.ProcessPoolExecutor or mpi4py.futures.MPIPool , optional - Allows for parallel processing. The default of None will use a - concurrent.futures.ProcessPoolExecutor with `nthreads` processes. - nthreads : int, optional - The number of threads to use in parallel processing. Only used if - ``pool`` is None to initialize concurrent.futures.ProcessPoolExecutor. Examples -------- @@ -96,8 +91,7 @@ class FilFinder2D(BaseInfoMixin): def __init__(self, image, header=None, beamwidth=None, ang_scale=None, distance=None, mask=None, save_name="FilFinder_output", - capture_pre_recombine_masks=False, - pool=None, nthreads=1): + capture_pre_recombine_masks=False): # Accepts a numpy array or fits.PrimaryHDU output = input_data(image, header) @@ -164,16 +158,6 @@ def __init__(self, image, header=None, beamwidth=None, ang_scale=None, self._pre_recombine_mask_objs = None self._pre_recombine_mask_corners = None - if pool is None: - try: - from loky import get_reusable_executor - pool = get_reusable_executor(max_workers=nthreads) - except ImportError: - import concurrent.futures - pool = concurrent.futures.ProcessPoolExecutor(max_workers=nthreads) - - self.pool = pool - def preprocess_image(self, skip_flatten=False, flatten_percent=None): ''' Preprocess and flatten the image before running the masking routine. @@ -186,7 +170,7 @@ def preprocess_image(self, skip_flatten=False, flatten_percent=None): flatten_percent : int, optional The percentile of the data (0-100) to set the normalization of the arctan transform. By default, a log-normal distribution is fit and - the threshold is set to mean + 2 * std. If the data contains + the threshold is set to :math:`\mu + 2\sigma`. If the data contains regions of a much higher intensity than the mean, it is recommended this be set >95 percentile. @@ -503,12 +487,6 @@ def create_mask(self, glob_thresh=None, adapt_thresh=None, if verbose or save_png: vmin = np.percentile(self.flat_img[np.isfinite(self.flat_img)], 20) vmax = np.percentile(self.flat_img[np.isfinite(self.flat_img)], 90) - - # if flat_img has a unit, remove from vmin and vmax - if hasattr(self.flat_img, "unit"): - vmin = vmin.value - vmax = vmax.value - p.clf() p.imshow(self.flat_img.value, interpolation='nearest', origin="lower", cmap='binary', vmin=vmin, vmax=vmax) @@ -575,12 +553,6 @@ def medskel(self, verbose=False, save_png=False, rng=None): if verbose or save_png: # For examining results of skeleton vmin = np.percentile(self.flat_img[np.isfinite(self.flat_img)], 20) vmax = np.percentile(self.flat_img[np.isfinite(self.flat_img)], 90) - - # if flat_img has a unit, remove from vmin and vmax - if hasattr(self.flat_img, "unit"): - vmin = vmin.value - vmax = vmax.value - p.clf() p.imshow(self.flat_img.value, interpolation=None, origin="lower", cmap='binary', vmin=vmin, vmax=vmax) @@ -593,6 +565,7 @@ def medskel(self, verbose=False, save_png=False, rng=None): p.clf() def analyze_skeletons(self, + nthreads=1, prune_criteria='all', relintens_thresh=0.2, nbeam_lengths=5, @@ -602,8 +575,7 @@ def analyze_skeletons(self, max_prune_iter=10, verbose=False, save_png=False, - save_name=None, - debug=False,): + save_name=None): ''' Prune skeleton structure and calculate the branch and longest-path @@ -612,6 +584,8 @@ def analyze_skeletons(self, Parameters ---------- + nthreads : int, optional + Number of threads to use to parallelize the skeleton analysis. prune_criteria : {'all', 'intensity', 'length'}, optional Choose the property to base pruning on. 'all' requires that the branch fails to satisfy the length and relative intensity checks. @@ -640,8 +614,6 @@ def analyze_skeletons(self, Saves the plot made in verbose mode. Disabled by default. save_name : str, optional Prefix for the saved plots. - debug : bool, optional - Enables debug mode with extra printing ''' if relintens_thresh > 1.0 or relintens_thresh <= 0.0: @@ -664,9 +636,7 @@ def analyze_skeletons(self, else: skel_thresh = self.converter.to_pixel(skel_thresh) - # Ensure the minimum is always >1 pixel. - - self.skel_thresh = min(np.ceil(skel_thresh), 1 * u.pix) + self.skel_thresh = np.ceil(skel_thresh) # Set the minimum branch length to be the beam size. if branch_thresh is None: @@ -679,45 +649,35 @@ def analyze_skeletons(self, # Label individual filaments and define the set of filament objects labels, num = nd.label(self.skeleton, eight_con()) - if debug: - print(f"Found {num} filaments before removing short skeletons") - # Find the objects that don't satisfy skel_thresh - obj_sums = nd.sum(self.skeleton, labels, range(1, num + 1)) - remove_fils = np.where(obj_sums <= self.skel_thresh.value)[0] - - for lab in remove_fils: - if debug: - print(f"Removing {lab} with {obj_sums[lab]} pixels") - self.skeleton[np.where(labels == lab + 1)] = 0 - - # Relabel after deleting short skeletons. - labels, num = nd.label(self.skeleton, eight_con()) + if self.skel_thresh > 0.: + obj_sums = nd.sum(self.skeleton, labels, range(1, num + 1)) + remove_fils = np.where(obj_sums <= self.skel_thresh.value)[0] - if debug: - print(f"Found {num} filaments after removing short skeletons") + for lab in remove_fils: + self.skeleton[np.where(labels == lab + 1)] = 0 - self.filaments = [] + # Relabel after deleting short skeletons. + labels, num = nd.label(self.skeleton, eight_con()) - for lab in range(1, num + 1): - if debug: - print(f"Filament {lab} has {np.sum(labels == lab)} pixels") - self.filaments.append(Filament2D(np.where(labels == lab), - converter=self.converter)) + self.filaments = [Filament2D(np.where(labels == lab), + converter=self.converter) for lab in + range(1, num + 1)] # Now loop over the skeleton analysis for each filament object - futures = [self.pool.submit(fil.skeleton_analysis, self.image, - verbose=verbose, - save_png=save_png, - save_name=save_name, - prune_criteria=prune_criteria, - relintens_thresh=relintens_thresh, - branch_thresh=self.branch_thresh, - max_prune_iter=max_prune_iter, - return_self=True) - for fil in self.filaments] - self.filaments = [future.result() for future in futures] + with concurrent.futures.ProcessPoolExecutor(nthreads) as executor: + futures = [executor.submit(fil.skeleton_analysis, self.image, + verbose=verbose, + save_png=save_png, + save_name=save_name, + prune_criteria=prune_criteria, + relintens_thresh=relintens_thresh, + branch_thresh=self.branch_thresh, + max_prune_iter=max_prune_iter, + return_self=True) + for fil in self.filaments] + self.filaments = [future.result() for future in futures] self.number_of_filaments = num @@ -752,18 +712,6 @@ def analyze_skeletons(self, self.array_offsets, self.image.shape, 0) - def make_skeleton_minbranchlength(self, branch_thresh): - ''' - Make a skeleton with a minimum branch length, ignoring connectivity - within individual skeletons. - ''' - - return recombine_skeletons([fil.skeleton(branch_thresh=branch_thresh, - out_type='minbranchlength') - for fil in self.filaments], - self.array_offsets, self.image.shape, - 0) - def lengths(self, unit=u.pix): ''' Return longest path lengths of the filaments. @@ -825,6 +773,7 @@ def end_pts(self): return [fil.end_pts for fil in self.filaments] def exec_rht(self, + nthreads=1, radius=10 * u.pix, ntheta=180, background_percentile=25, branches=False, min_branch_length=3 * u.pix, @@ -850,6 +799,8 @@ def exec_rht(self, Parameters ---------- + nthreads : int, optional + The number of threads to use. radius : int Sets the patch size that the RHT uses. ntheta : int, optional @@ -892,24 +843,26 @@ def exec_rht(self, if branches: - futures = [self.pool.submit(fil.rht_branch_analysis, - radius=radius, - ntheta=ntheta, - background_percentile=background_percentile, - min_branch_length=min_branch_length, - return_self=True) - for fil in self.filaments] - self.filaments = [future.result() for future in futures] + with concurrent.futures.ProcessPoolExecutor(nthreads) as executor: + futures = [executor.submit(fil.rht_branch_analysis, + radius=radius, + ntheta=ntheta, + background_percentile=background_percentile, + min_branch_length=min_branch_length, + return_self=True) + for fil in self.filaments] + self.filaments = [future.result() for future in futures] else: - futures = [self.pool.submit(fil.rht_analysis, - radius=radius, - ntheta=ntheta, - background_percentile=background_percentile, - return_self=True) - for fil in self.filaments] - self.filaments = [future.result() for future in futures] + with concurrent.futures.ProcessPoolExecutor(nthreads) as executor: + futures = [executor.submit(fil.rht_analysis, + radius=radius, + ntheta=ntheta, + background_percentile=background_percentile, + return_self=True) + for fil in self.filaments] + self.filaments = [future.result() for future in futures] if verbose: @@ -974,6 +927,7 @@ def pre_recombine_mask_corners(self): return self._pre_recombine_mask_corners def find_widths(self, + nthreads=1, max_dist=10 * u.pix, pad_to_distance=0 * u.pix, fit_model='gaussian_bkg', @@ -1003,6 +957,8 @@ def find_widths(self, Parameters ---------- + nthreads : int, optional + Number of threads to use. max_dist : `~astropy.units.Quantity`, optional Largest radius around the skeleton to create the profile from. This can be given in physical, angular, or physical units. @@ -1049,22 +1005,23 @@ def find_widths(self, if save_name is None: save_name = self.save_name - futures = [self.pool.submit(fil.width_analysis, self.image, - all_skeleton_array=self.skeleton, - max_dist=max_dist, - pad_to_distance=pad_to_distance, - fit_model=fit_model, - fitter=fitter, try_nonparam=try_nonparam, - use_longest_path=use_longest_path, - add_width_to_length=add_width_to_length, - deconvolve_width=deconvolve_width, - beamwidth=self.beamwidth, - fwhm_function=fwhm_function, - chisq_max=chisq_max, - return_self=True, - **kwargs) - for fil in self.filaments] - self.filaments = [future.result() for future in futures] + with concurrent.futures.ProcessPoolExecutor(nthreads) as executor: + futures = [executor.submit(fil.width_analysis, self.image, + all_skeleton_array=self.skeleton, + max_dist=max_dist, + pad_to_distance=pad_to_distance, + fit_model=fit_model, + fitter=fitter, try_nonparam=try_nonparam, + use_longest_path=use_longest_path, + add_width_to_length=add_width_to_length, + deconvolve_width=deconvolve_width, + beamwidth=self.beamwidth, + fwhm_function=fwhm_function, + chisq_max=chisq_max, + return_self=True, + **kwargs) + for fil in self.filaments] + self.filaments = [future.result() for future in futures] for n, fil in enumerate(self.filaments): @@ -1305,8 +1262,8 @@ def output_table(self, xunit=u.pix, world_coord=False, **kwargs): Return the median filament position in world coordinates. kwargs : Passed to `~FilFinder2D.total_intensity`. - Returns - ------- + Return + ------ tab : `~astropy.table.Table` Table with all analyzed parameters. ''' @@ -1406,7 +1363,7 @@ def save_fits(self, save_name=None, kwargs : Passed to `~astropy.io.fits.PrimaryHDU.writeto`. ''' - + if save_name is None: save_name = self.save_name else: @@ -1461,13 +1418,24 @@ def save_fits(self, save_name=None, str(self.branch_thresh) # Final Skeletons - create labels which match up with table output - - labels = nd.label(self.skeleton, eight_con())[0] + ## Create labelled skeleton map with fil IDs + ids = np.zeros(self.image.shape) + + for n, fil in enumerate(self.filaments): #Index starts at 0, bump up 1 to maintain mask + ids[fil.pixel_coords] = n+1 + + labels = nd.label(ids, eight_con())[0] #still saving skeleton, now ID matched + #labels = nd.label(self.skeleton, eight_con())[0] out_hdu.append(fits.ImageHDU(labels, header=new_hdr_skel)) # Longest Paths if save_longpath_skeletons: - labels_lp = nd.label(self.skeleton_longpath, eight_con())[0] + idl = np.zeros(self.image.shape) + for n, fil in enumerate(self.filaments): + idl[fil.longpath_pixel_coords] = n+1 + + labels_lp = nd.label(idl, eight_con())[0] + #labels_lp = nd.label(self.skeleton_longpath, eight_con())[0] out_hdu.append(fits.ImageHDU(labels_lp, header=new_hdr_skel)) @@ -1497,7 +1465,6 @@ def save_stamp_fits(self, image_dict=None, save_name=None, pad_size=0 * u.pix, - save_model=True, model_kwargs={}, **kwargs): ''' @@ -1518,8 +1485,6 @@ def save_stamp_fits(self, when `~FilFinder2D` was first called. stamps : bool, optional Enables saving of individual stamps - save_model : bool, optional - Save the model image. Defaults to True. Set to False if no width model has been fit. model_kwargs : dict, optional Passed to `~FilFinder2D.filament_model`. kwargs : Passed to `~astropy.io.fits.PrimaryHDU.writeto`. @@ -1546,6 +1511,5 @@ def save_stamp_fits(self, self.image, image_dict=image_dict, pad_size=pad_size, - save_model=save_model, model_kwargs=model_kwargs, **kwargs) From 403be3a0557adf431abe1d6a4dce29ec67accdb9 Mon Sep 17 00:00:00 2001 From: Rachel Pillsworth Date: Tue, 25 Nov 2025 10:00:28 -0800 Subject: [PATCH 2/3] ID-match skeleton maps to save_fits - uptodate --- fil_finder/filfinder2D.py | 197 +++++++++++++++++++++++--------------- 1 file changed, 122 insertions(+), 75 deletions(-) diff --git a/fil_finder/filfinder2D.py b/fil_finder/filfinder2D.py index 0cf0ee7..fb32417 100644 --- a/fil_finder/filfinder2D.py +++ b/fil_finder/filfinder2D.py @@ -14,7 +14,6 @@ import os import time import warnings -import concurrent.futures from .pixel_ident import recombine_skeletons, isolateregions from .utilities import eight_con, round_to_odd, threshold_local, in_ipynb @@ -69,6 +68,12 @@ class FilFinder2D(BaseInfoMixin): save_name : str, optional Sets the prefix name that is used for output files. Can be overridden in ``save_fits`` and ``save_table``. Default is "FilFinder_output". + pool : None, concurrent.futures.ProcessPoolExecutor or mpi4py.futures.MPIPool , optional + Allows for parallel processing. The default of None will use a + concurrent.futures.ProcessPoolExecutor with `nthreads` processes. + nthreads : int, optional + The number of threads to use in parallel processing. Only used if + ``pool`` is None to initialize concurrent.futures.ProcessPoolExecutor. Examples -------- @@ -91,7 +96,8 @@ class FilFinder2D(BaseInfoMixin): def __init__(self, image, header=None, beamwidth=None, ang_scale=None, distance=None, mask=None, save_name="FilFinder_output", - capture_pre_recombine_masks=False): + capture_pre_recombine_masks=False, + pool=None, nthreads=1): # Accepts a numpy array or fits.PrimaryHDU output = input_data(image, header) @@ -158,6 +164,16 @@ def __init__(self, image, header=None, beamwidth=None, ang_scale=None, self._pre_recombine_mask_objs = None self._pre_recombine_mask_corners = None + if pool is None: + try: + from loky import get_reusable_executor + pool = get_reusable_executor(max_workers=nthreads) + except ImportError: + import concurrent.futures + pool = concurrent.futures.ProcessPoolExecutor(max_workers=nthreads) + + self.pool = pool + def preprocess_image(self, skip_flatten=False, flatten_percent=None): ''' Preprocess and flatten the image before running the masking routine. @@ -170,7 +186,7 @@ def preprocess_image(self, skip_flatten=False, flatten_percent=None): flatten_percent : int, optional The percentile of the data (0-100) to set the normalization of the arctan transform. By default, a log-normal distribution is fit and - the threshold is set to :math:`\mu + 2\sigma`. If the data contains + the threshold is set to mean + 2 * std. If the data contains regions of a much higher intensity than the mean, it is recommended this be set >95 percentile. @@ -487,6 +503,12 @@ def create_mask(self, glob_thresh=None, adapt_thresh=None, if verbose or save_png: vmin = np.percentile(self.flat_img[np.isfinite(self.flat_img)], 20) vmax = np.percentile(self.flat_img[np.isfinite(self.flat_img)], 90) + + # if flat_img has a unit, remove from vmin and vmax + if hasattr(self.flat_img, "unit"): + vmin = vmin.value + vmax = vmax.value + p.clf() p.imshow(self.flat_img.value, interpolation='nearest', origin="lower", cmap='binary', vmin=vmin, vmax=vmax) @@ -553,6 +575,12 @@ def medskel(self, verbose=False, save_png=False, rng=None): if verbose or save_png: # For examining results of skeleton vmin = np.percentile(self.flat_img[np.isfinite(self.flat_img)], 20) vmax = np.percentile(self.flat_img[np.isfinite(self.flat_img)], 90) + + # if flat_img has a unit, remove from vmin and vmax + if hasattr(self.flat_img, "unit"): + vmin = vmin.value + vmax = vmax.value + p.clf() p.imshow(self.flat_img.value, interpolation=None, origin="lower", cmap='binary', vmin=vmin, vmax=vmax) @@ -565,7 +593,6 @@ def medskel(self, verbose=False, save_png=False, rng=None): p.clf() def analyze_skeletons(self, - nthreads=1, prune_criteria='all', relintens_thresh=0.2, nbeam_lengths=5, @@ -575,7 +602,8 @@ def analyze_skeletons(self, max_prune_iter=10, verbose=False, save_png=False, - save_name=None): + save_name=None, + debug=False,): ''' Prune skeleton structure and calculate the branch and longest-path @@ -584,8 +612,6 @@ def analyze_skeletons(self, Parameters ---------- - nthreads : int, optional - Number of threads to use to parallelize the skeleton analysis. prune_criteria : {'all', 'intensity', 'length'}, optional Choose the property to base pruning on. 'all' requires that the branch fails to satisfy the length and relative intensity checks. @@ -614,6 +640,8 @@ def analyze_skeletons(self, Saves the plot made in verbose mode. Disabled by default. save_name : str, optional Prefix for the saved plots. + debug : bool, optional + Enables debug mode with extra printing ''' if relintens_thresh > 1.0 or relintens_thresh <= 0.0: @@ -636,7 +664,9 @@ def analyze_skeletons(self, else: skel_thresh = self.converter.to_pixel(skel_thresh) - self.skel_thresh = np.ceil(skel_thresh) + # Ensure the minimum is always >1 pixel. + + self.skel_thresh = min(np.ceil(skel_thresh), 1 * u.pix) # Set the minimum branch length to be the beam size. if branch_thresh is None: @@ -649,35 +679,45 @@ def analyze_skeletons(self, # Label individual filaments and define the set of filament objects labels, num = nd.label(self.skeleton, eight_con()) + if debug: + print(f"Found {num} filaments before removing short skeletons") + # Find the objects that don't satisfy skel_thresh - if self.skel_thresh > 0.: - obj_sums = nd.sum(self.skeleton, labels, range(1, num + 1)) - remove_fils = np.where(obj_sums <= self.skel_thresh.value)[0] + obj_sums = nd.sum(self.skeleton, labels, range(1, num + 1)) + remove_fils = np.where(obj_sums <= self.skel_thresh.value)[0] + + for lab in remove_fils: + if debug: + print(f"Removing {lab} with {obj_sums[lab]} pixels") + self.skeleton[np.where(labels == lab + 1)] = 0 - for lab in remove_fils: - self.skeleton[np.where(labels == lab + 1)] = 0 + # Relabel after deleting short skeletons. + labels, num = nd.label(self.skeleton, eight_con()) + + if debug: + print(f"Found {num} filaments after removing short skeletons") - # Relabel after deleting short skeletons. - labels, num = nd.label(self.skeleton, eight_con()) + self.filaments = [] + for lab in range(1, num + 1): + if debug: + print(f"Filament {lab} has {np.sum(labels == lab)} pixels") - self.filaments = [Filament2D(np.where(labels == lab), - converter=self.converter) for lab in - range(1, num + 1)] + self.filaments.append(Filament2D(np.where(labels == lab), + converter=self.converter)) # Now loop over the skeleton analysis for each filament object - with concurrent.futures.ProcessPoolExecutor(nthreads) as executor: - futures = [executor.submit(fil.skeleton_analysis, self.image, - verbose=verbose, - save_png=save_png, - save_name=save_name, - prune_criteria=prune_criteria, - relintens_thresh=relintens_thresh, - branch_thresh=self.branch_thresh, - max_prune_iter=max_prune_iter, - return_self=True) - for fil in self.filaments] - self.filaments = [future.result() for future in futures] + futures = [self.pool.submit(fil.skeleton_analysis, self.image, + verbose=verbose, + save_png=save_png, + save_name=save_name, + prune_criteria=prune_criteria, + relintens_thresh=relintens_thresh, + branch_thresh=self.branch_thresh, + max_prune_iter=max_prune_iter, + return_self=True) + for fil in self.filaments] + self.filaments = [future.result() for future in futures] self.number_of_filaments = num @@ -712,6 +752,18 @@ def analyze_skeletons(self, self.array_offsets, self.image.shape, 0) + def make_skeleton_minbranchlength(self, branch_thresh): + ''' + Make a skeleton with a minimum branch length, ignoring connectivity + within individual skeletons. + ''' + + return recombine_skeletons([fil.skeleton(branch_thresh=branch_thresh, + out_type='minbranchlength') + for fil in self.filaments], + self.array_offsets, self.image.shape, + 0) + def lengths(self, unit=u.pix): ''' Return longest path lengths of the filaments. @@ -773,7 +825,6 @@ def end_pts(self): return [fil.end_pts for fil in self.filaments] def exec_rht(self, - nthreads=1, radius=10 * u.pix, ntheta=180, background_percentile=25, branches=False, min_branch_length=3 * u.pix, @@ -799,8 +850,6 @@ def exec_rht(self, Parameters ---------- - nthreads : int, optional - The number of threads to use. radius : int Sets the patch size that the RHT uses. ntheta : int, optional @@ -843,26 +892,24 @@ def exec_rht(self, if branches: - with concurrent.futures.ProcessPoolExecutor(nthreads) as executor: - futures = [executor.submit(fil.rht_branch_analysis, - radius=radius, - ntheta=ntheta, - background_percentile=background_percentile, - min_branch_length=min_branch_length, - return_self=True) - for fil in self.filaments] - self.filaments = [future.result() for future in futures] + futures = [self.pool.submit(fil.rht_branch_analysis, + radius=radius, + ntheta=ntheta, + background_percentile=background_percentile, + min_branch_length=min_branch_length, + return_self=True) + for fil in self.filaments] + self.filaments = [future.result() for future in futures] else: - with concurrent.futures.ProcessPoolExecutor(nthreads) as executor: - futures = [executor.submit(fil.rht_analysis, - radius=radius, - ntheta=ntheta, - background_percentile=background_percentile, - return_self=True) - for fil in self.filaments] - self.filaments = [future.result() for future in futures] + futures = [self.pool.submit(fil.rht_analysis, + radius=radius, + ntheta=ntheta, + background_percentile=background_percentile, + return_self=True) + for fil in self.filaments] + self.filaments = [future.result() for future in futures] if verbose: @@ -927,7 +974,6 @@ def pre_recombine_mask_corners(self): return self._pre_recombine_mask_corners def find_widths(self, - nthreads=1, max_dist=10 * u.pix, pad_to_distance=0 * u.pix, fit_model='gaussian_bkg', @@ -957,8 +1003,6 @@ def find_widths(self, Parameters ---------- - nthreads : int, optional - Number of threads to use. max_dist : `~astropy.units.Quantity`, optional Largest radius around the skeleton to create the profile from. This can be given in physical, angular, or physical units. @@ -1005,23 +1049,22 @@ def find_widths(self, if save_name is None: save_name = self.save_name - with concurrent.futures.ProcessPoolExecutor(nthreads) as executor: - futures = [executor.submit(fil.width_analysis, self.image, - all_skeleton_array=self.skeleton, - max_dist=max_dist, - pad_to_distance=pad_to_distance, - fit_model=fit_model, - fitter=fitter, try_nonparam=try_nonparam, - use_longest_path=use_longest_path, - add_width_to_length=add_width_to_length, - deconvolve_width=deconvolve_width, - beamwidth=self.beamwidth, - fwhm_function=fwhm_function, - chisq_max=chisq_max, - return_self=True, - **kwargs) - for fil in self.filaments] - self.filaments = [future.result() for future in futures] + futures = [self.pool.submit(fil.width_analysis, self.image, + all_skeleton_array=self.skeleton, + max_dist=max_dist, + pad_to_distance=pad_to_distance, + fit_model=fit_model, + fitter=fitter, try_nonparam=try_nonparam, + use_longest_path=use_longest_path, + add_width_to_length=add_width_to_length, + deconvolve_width=deconvolve_width, + beamwidth=self.beamwidth, + fwhm_function=fwhm_function, + chisq_max=chisq_max, + return_self=True, + **kwargs) + for fil in self.filaments] + self.filaments = [future.result() for future in futures] for n, fil in enumerate(self.filaments): @@ -1262,8 +1305,8 @@ def output_table(self, xunit=u.pix, world_coord=False, **kwargs): Return the median filament position in world coordinates. kwargs : Passed to `~FilFinder2D.total_intensity`. - Return - ------ + Returns + ------- tab : `~astropy.table.Table` Table with all analyzed parameters. ''' @@ -1363,7 +1406,7 @@ def save_fits(self, save_name=None, kwargs : Passed to `~astropy.io.fits.PrimaryHDU.writeto`. ''' - + if save_name is None: save_name = self.save_name else: @@ -1423,7 +1466,7 @@ def save_fits(self, save_name=None, for n, fil in enumerate(self.filaments): #Index starts at 0, bump up 1 to maintain mask ids[fil.pixel_coords] = n+1 - + labels = nd.label(ids, eight_con())[0] #still saving skeleton, now ID matched #labels = nd.label(self.skeleton, eight_con())[0] out_hdu.append(fits.ImageHDU(labels, header=new_hdr_skel)) @@ -1435,7 +1478,7 @@ def save_fits(self, save_name=None, idl[fil.longpath_pixel_coords] = n+1 labels_lp = nd.label(idl, eight_con())[0] - #labels_lp = nd.label(self.skeleton_longpath, eight_con())[0] + #labels_lp = nd.label(self.skeleton_longpath, eight_con())[0] out_hdu.append(fits.ImageHDU(labels_lp, header=new_hdr_skel)) @@ -1465,6 +1508,7 @@ def save_stamp_fits(self, image_dict=None, save_name=None, pad_size=0 * u.pix, + save_model=True, model_kwargs={}, **kwargs): ''' @@ -1485,6 +1529,8 @@ def save_stamp_fits(self, when `~FilFinder2D` was first called. stamps : bool, optional Enables saving of individual stamps + save_model : bool, optional + Save the model image. Defaults to True. Set to False if no width model has been fit. model_kwargs : dict, optional Passed to `~FilFinder2D.filament_model`. kwargs : Passed to `~astropy.io.fits.PrimaryHDU.writeto`. @@ -1511,5 +1557,6 @@ def save_stamp_fits(self, self.image, image_dict=image_dict, pad_size=pad_size, + save_model=save_model, model_kwargs=model_kwargs, **kwargs) From f9be4e7641055eead0f9398efb3260d21f336e13 Mon Sep 17 00:00:00 2001 From: Eric Koch Date: Wed, 26 Nov 2025 12:46:44 -0500 Subject: [PATCH 3/3] remove unused code --- fil_finder/filfinder2D.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fil_finder/filfinder2D.py b/fil_finder/filfinder2D.py index fb32417..d35c764 100644 --- a/fil_finder/filfinder2D.py +++ b/fil_finder/filfinder2D.py @@ -1478,7 +1478,7 @@ def save_fits(self, save_name=None, idl[fil.longpath_pixel_coords] = n+1 labels_lp = nd.label(idl, eight_con())[0] - #labels_lp = nd.label(self.skeleton_longpath, eight_con())[0] + out_hdu.append(fits.ImageHDU(labels_lp, header=new_hdr_skel))