Installing Fil3D ================ .. toctree:: Installation ------------ Fil3D can `eventually` be installed via pip (recommended, not available yet): .. code-block:: shell pip install fil3d Or you can install directly from this repo (this will always fetch ``HEAD`` - do this for now if you just want to use the package): .. code-block:: shell pip install git+https://github.com/LLi1996/fil3d Or you can install directly from this repo in editable mode (this will always fetch ``HEAD`` - do this if you want to use the package and maybe poke around the code as well): .. code-block:: shell pip install -e git+https://github.com/LLi1996/fil3d#egg=fil3d Of course you can also just install with ``setup.py`` after cloning this repo locally (this is the same as pip install -e but with more steps): .. code-block:: shell git clone https://github.com/LLi1996/fil3d.git cd fil3d python setup.py install Requirements ------------ Requires: * astropy * matplotlib * numpy * scipy Optional: * FilFinder (`docs `_) Quickstart ---------- We separate data and masks. Data in this case refer to data cubes, data slices, FITS files, numpy arrays, etc. - whatever you use to to store your data. Masks in this case refers to a 2D numpy array bit-mask (and the associated corners of that mask). Masks are the primary objects in most parts of the program - velocity-aware mask objects are compared, merged, and stacked to create representations (bounds) of filaments in 3D (P-P-V) space that we call trees. When we need pixel values to analyse individual filaments, these collections of masks (trees) are used to pull values out of whatever data storage systems / formats that you prefer. 2D mask container objects are the base units. A mask object can be instantiated with three components: 1) the 2D bit mask (this is self-explanatory), 2) the corners which matches the dimensions of this 2D bit mask (this will be used to locate the mask within the greater data slice/cube, so that this mask can be compared to other masks and this mask object can be used to select data from your data files), and 3) the index of the velocity channel where this mask belongs (it is important that you assign some sequential ordering to your velocity slices - it doesn't matter if you choose ascending or descending - but note that mask objects which do not reside on neighboring velocity slices will not be considered for matching later in the program). Note: you should set the ``fil3d._const.NAXIS_X`` and ``fil3d._const.NAXIS_Y`` values. If not set they will default to the GALFA full slice values. These constants are used to make sure that ``MaskObjNode`` objects do not contain out of bound corners. Using your own masks ____________________ If you already have masks lying around, you can plug those into the base :ref:`MaskObjNode-label`, like this: .. code-block:: python import numpy as np from fil3d import MaskObjNode # some 4x4 mask, with 6 pixels ON and 10 pixels OFF mask = np.asarray([[0, 0, 0, 0], [1, 1, 1, 0], [0, 1, 1, 1], [0, 0, 0, 0]], bool) # [[y0, x0], [y1, x1]] in numpy indexing, note our 4x4 mask needs to fit in the corners corners = [[0, 1], [4, 5]] # let's say this mask is for data residing on the third velocity slice in your collection v_index = 2 mask_obj = MaskObjNode(mask, corners, v_index) # if you have multiple of these masks and their corners and velocity indexes: mask_obj_dict = {} for mask_obj in list_of_mask_objs: MaskObjNode.add_node_to_dict(mask_obj, mask_obj_dict) Getting masks from FilFinder ____________________________ If you are using `FilFinder `_, you can pluck the masks and corners from ``FilFinder2D`` objects that you've already created. To do so, you have to set ``capture_pre_recombine_masks=True`` at object instantiation to enable the caching of masks before they're combined into a single skeleton. Currently this feature is in preview for FilFinder 1.18 (as of 2021-06-24, the latest pip-installable version on pypi is 1.17). Don't worry though - you can still access this feature by directly building FilFinder from the source repo master like this: .. code-block:: shell pip install git+https://github.com/e-koch/FilFinder After running ``create_mask()`` with ``use_existing_mask=False``, you will be able to access the ``pre_recombine_mask_objs`` and ``pre_recombine_mask_corners`` properties of the instance. For this quick start run-through, we'll be using a data cube with a few injected filaments located on the github repo under ``data/examples/fil_injection/``. .. code-block:: python import numpy as np from astropy import units as u from astropy.io.fits import Header from fil_finder import FilFinder2D from fil3d import MaskObjNode # 50 x 300 x 500 data cube (v, y, x) data_cube = np.load('data/examples/fil_injection/fil_injection.npz')['data'] # let's say the image fed into FilFinder resides on the third velocity slice in your collection v_index = 2 data_slice = data_cube[v_index] # creating a fake header to make our lives a little easier hdr = Header(dict( CTYPE1='RA', CDELT1=-0.0166667, CTYPE2='DEC', CDELT2=0.0166667, BUNIT='K' )) # most of this follows the FilFinder2D docs fil = FilFinder2D(data_slice, header=hdr, distance=100. * u.pc, beamwidth=10 * u.arcmin, capture_pre_recombine_masks=True) fil.preprocess_image(flatten_percent=95) fil.create_mask() # there might be multiple masks in a single FilFinder2D instance # here we store them in a dictionary for ease of access, with an arbitrary numerical key mask_obj_dict = {} for i in range(len(fil.pre_recombine_mask_objs)): MaskObjNode.add_node_to_dict(MaskObjNode(fil.pre_recombine_mask_objs[i], fil.pre_recombine_mask_corners[i], v_index), mask_obj_dict) Going from 2D masks to 3D trees _______________________________ One of the main advantanges of working with masks is that we don't have to carry data around when we're performing mask matching operations between velocity slices. For the purpose of this tutorial, we'll be using processed dictionaries of masks for each velocity channel in the example data cube stored in github under ``data/examples/fil_injection/mask_dictionaries/``. Using the process outlined in :ref:`MasksToTrees-label`, we match mask objects on neighboring velocity channels to build "trees" in the 3D (P-P-V) space. The utility function ``find_all_trees_from_slices()`` can be used like this: .. code-block:: python import pickle from fil3d.util.tree_dict_util import find_all_trees_from_slices velocity_channels = range(50) mask_dict_paths = [f'data/examples/fil_injection/mask_dictionaries/{i}.PICKLE' for i in range(50)] tree_dict = find_all_trees_from_slices(vs=velocity_channels, dict_full_paths=mask_dict_paths, overlap_thresh=.85) This will return a dictionary of trees. Using trees to access data __________________________ WIP