This tutorial demonstrates the usage of the FilFinder2D
object to perform the analysis on an individual filament. The general tutorial for FilFinder can be found here.
In [1]:
%matplotlib inline
import matplotlib.pyplot as plt
import astropy.units as u
# Optional settings for the plots. Comment out if needed.
import seaborn as sb
sb.set_context('poster')
import matplotlib as mpl
mpl.rcParams['figure.figsize'] = (12., 9.6)
The general tutorial demonstrates how the Filament2D
objects interact with FilFinder2D
. To generate an example, we will use a straight Gaussian filament to demonstrate how the FilFinder analysis can be run on individual filaments. We can also create a simplified mask by simply thresholding the image.
In [2]:
from fil_finder import FilFinder2D, Filament2D
from fil_finder.tests.testing_utils import generate_filament_model
mod = generate_filament_model(return_hdu=True, pad_size=30, shape=150,
width=10., background=0.1)[0]
# Create a simple filament mask
mask = mod.data > 0.5
plt.imshow(mod.data, origin='lower')
plt.colorbar()
plt.contour(mask, colors='c', linestyles='--')
Out[2]:
The simple filament model is shown above, with the mask highlighted by the dashed contour.
The first step is to run the first few steps on the FilFinder algorithm with FilFinder2D
:
In [3]:
filfind = FilFinder2D(mod, distance=250 * u.pc, mask=mask)
filfind.preprocess_image(flatten_percent=85)
filfind.create_mask(border_masking=True, verbose=False,
use_existing_mask=True)
filfind.medskel(verbose=False)
filfind.analyze_skeletons()
The Filament2D
objects are created when running FilFinder2D.analyze_skeleton
. From here, we will focus on the filament object:
In [4]:
fil = filfind.filaments[0]
The Filament2D
object contains a minimized version of the FilFinder algorithm. Most of the FilFinder2D
functionality simply loops throught the Filament2D
objects. However, Filament2D
object do not keep a copy of the data and are designed to carry a minimal amount of information.
Though not shown here, the only requirement to create a Filament2D
object is a set of pixels that define the skeleton shape. These are contained in:
In [5]:
fil.pixel_coords
Out[5]:
These pixel coordinates are the positions in the original image. When the skeleton array is generated, the minimal shape is returned:
In [6]:
plt.imshow(fil.skeleton())
Out[6]:
For a straight skeleton in this case, this returns a 1-pixel wide array in 0th dimension. This array can be padded:
In [7]:
plt.imshow(fil.skeleton(pad_size=10))
Out[7]:
The position of the filament is defined as the median pixel location based on the set of skeleton pixels:
In [8]:
fil.position()
Out[8]:
If WCS information is given for the object, the centre can also be returned in world coordinates:
In [9]:
fil.position(world_coord=True)
Out[9]:
The skeleton analysis, equivalent to FilFinder2D.analyze_skeletons
is Filament2D.skeleton_analysis
. However, there are additional arguments that must be passed since the Filament2D
object does not contain a copy of the image. To reproduce the fil.analyze_skeletons
call from above, we can run:
In [10]:
fil.skeleton_analysis(filfind.image, verbose=True)
The same keyword and plot output is shown as described in the FilFinder2D tutorial.
The networkx graph can be accessed from fil.graph
and can be plotted:
In [11]:
fil.plot_graph()
Filaments with multiple branches have more interesting looking graphs.
The lengths and branch properties are now defined:
In [12]:
fil.length()
Out[12]:
In [13]:
fil.branch_properties.keys()
Out[13]:
The longest path skeleton is now defined as well. The skeleton array of the longest path can be returned with:
In [14]:
plt.imshow(fil.skeleton(out_type='longpath', pad_size=10), origin='lower')
Out[14]:
In [15]:
fil.rht_analysis()
print(fil.orientation, fil.curvature)
The RHT distribution can be plotted with:
In [16]:
fil.plot_rht_distrib()
And the distribution can be accessed with Filament2D.orientation_hist
, which contains the bins and the distribution values.
To run on individual branches, use:
In [17]:
fil.rht_branch_analysis()
print(fil.orientation_branches, fil.curvature_branches)
The properties of branches can be returned with:
In [18]:
fil.branch_table()
Out[18]:
And with the orientation and curvature branch information:
In [19]:
fil.branch_table(include_rht=True)
Out[19]:
The radial profiles and width analysis is run with Filament2D.width_analysis
. Most of the inputs are the same as those for FilFinder2D.find_widths
, with a few key differences:
Filament2D
is unaware of other filaments.To reproduce the FilFinder2D
analysis:
In [20]:
fil.width_analysis(filfind.image, all_skeleton_array=filfind.skeleton, beamwidth=filfind.beamwidth,
max_dist=0.3 * u.pc)
The radial profile is contained in fil.radprofile
and can be plotted with:
In [21]:
fil.plot_radial_profile(xunit=u.pc)
The parameters, uncertainty, model type, and check for fit quality are contained in:
In [22]:
print(fil.radprof_parnames)
print(fil.radprof_params)
print(fil.radprof_errors)
print(fil.radprof_type)
print(fil.radprof_fit_fail_flag)
The (possibly) deconvolved FWHM is:
In [23]:
fil.radprof_fwhm(u.pc)
Out[23]:
The first element is the FWHM and the second is the error.
Note that the fit has correctly recovered the model parameters set at the beginning.
An astropy table can be returned with the complete fit results:
In [24]:
fil.radprof_fit_table(unit=u.pc)
Out[24]:
And finally the fit model itself can be accessed with:
In [25]:
fil.radprof_model
Out[25]:
The radial profile can be returned as an astropy table with fil.radprof_table(xunit=u.pc)
.
In [26]:
fil.total_intensity()
Out[26]:
And with the fitted background removed:
In [27]:
fil.total_intensity(bkg_subtract=True)
Out[27]:
The model image from the radial profile fit:
In [28]:
plt.imshow(fil.model_image(), origin='lower')
plt.colorbar()
Out[28]:
By default, the background level is subtraced. Without the subtraction:
In [29]:
plt.imshow(fil.model_image(bkg_subtract=False), origin='lower')
plt.colorbar()
Out[29]:
The median along the skeleton:
In [30]:
fil.median_brightness(filfind.image)
Out[30]:
This is consistent with the max of the model image:
In [31]:
filfind.image.max()
Out[31]:
The profile along the longest path skeleton:
In [32]:
plt.plot(fil.ridge_profile(filfind.image))
Out[32]:
In this case, the ridge is constant in the model.
One feature that is not included in FilFinder2D
is Filament2D.profile_analysis
, which creates a set of profiles perpendicular to the longest path skeleton. This is useful for measuring filament properties as a function of position, rather than creating a single radial profile:
In [33]:
profs = fil.profile_analysis(filfind.image, xunit=u.pc, max_dist=30 * u.pix)
for dist, prof in zip(profs[0], profs[1]):
plt.plot(dist, prof)
plt.ylabel("Amplitude")
plt.xlabel("Distance from skeleton (pc)")
Out[33]:
Plotted here are all of the radial profiles. For this model, however, they are all the same. Fitting of the radial profiles is not included in Filament2D
due to the complexity that some radial slices can show in real filaments. See a dedicated package for this type of analysis, such as radfil.
A FITS file can be saved with a stamp of the image, skeleton, longest path skeleton, and the filament model:
In [34]:
fil.save_fits("filament_stamp.fits", filfind.image)
In [35]:
fil.to_pickle('filament.pkl')
In [36]:
loaded_fil = Filament2D.from_pickle('filament.pkl')
loaded_fil.length()
Out[36]: