Skip to content

Basic Usage

RuralBrick edited this page Oct 28, 2024 · 19 revisions

All About analyze_recording

To start getting results, all you need to know is PyAngstrom's analyze_recording! You can import it like so

from pyangstrom import analyze_recording

To use analyze_recording, you only need two pieces of data -- a thermal video of your sample material and a configuration file specifying how to analyze the video. (Currently, this library can only read the CSV exports of FLIR thermal cameras. If you need support for a different format of thermal video, please submit an issue with the title "Thermal Video Request: [the format of your thermal video]" and add more details in the description. We apologize for the inconvenience of this requesting system, as we do not have the resources to revamp it yet.)

The thermal video and configuration file are respectively passed into analyze_recording as a path recording_path and as either a dict or another path config.

For example, if you had a directory Rec-000001 with CSV temperature files in it:

C:\Users\Name\Documents\Rec-000001\
    Rec-000001_1.csv
    Rec-000001_2.csv
    Rec-000001_3.csv
    ...

you would pass in r"C:\Users\Name\Documents\Rec-000001\" for recording_path. (Confused about the "r" before the quotes? Read this: https://realpython.com/python-raw-strings/.)

Next, you pass in a specifically formatted dict (we will go over the exact format below) or the path to a JSON file with the same information into config. For example, if you had a configuration file Rec-000001_config.json located in folder C:\Users\Name\Documents, you would pass r"C:\Users\Name\Documents\Rec-000001_config.json" in for config.

In summary, to use analyze_recording, you need to call it with some code that looks like this (raw string not used here because not all markdown renderers recognize it):

analyze_recording(
    "C:\\Users\\Name\\Documents\\Rec-000001",
    "C:\\Users\\Name\\Documents\\Rec-000001_config.json",
)

It will return different values depending on exactly how you have configured it.

Now, what exactly does Rec-000001_config.json look like?

config's Format

Here's the moment you have all been waiting for: a full example of a configuration file!

// Rec-000001_config.json
{
    "experimental_setup": {
        "heating_frequency_hertz": 0.1,
        "meters_per_pixel": 2.5e-05,
        "material_properties": {
            "specific_heat_capacity_J__kg_K": 385.0,
            "density_kg__m3": 8960.0
        }
    },
    "region_information": {
        "geometry": {
            "min_x_pixels": 348,
            "max_x_pixels": 521,
            "min_y_pixels": 103,
            "max_y_pixels": 311,
            "heat_source_x_pixels": 558
        },
        "structure": {
            "average_out_span": true,
            "num_deinterleaving_groups": 5
        }
    },
    "signal_processor": {
        "name": "fft",
        "apply_filter": false
    },
    "solver": {
        "name": "log_lopez-baeza",
        "guesses": {
            "thermal_diffusivity_log10_m2__s": -5,
            "convective_heat_transfer_coefficient_log10_W__m2_K": -2
        },
        "parameters": {
            "r_meters": 0.00137,
            "length_meters": 0.0408
        }
    },
    "fitter": {
        "name": "nelder-mead",
        "parameters": {
            "properties_to_use": "phase-amplitude"
        }
    }
}

Now, if you are using this library, you probably know that performing the Angstrom method requires a few steps. analyze_region runs your thermal video through a pipeline that is broken up into three stages:

  1. Crop out the region of the video you actually want to analyze.
  2. Use signal processing to extract out temperature amplitude ratios and phase differences.
  3. Fit a heat model to the ratios and differences to compute your final thermal conductivity!

You specify exactly how analyze_region performs each of these stages in config. config itself is also split into multiple sections. We will go over each section in detail below, but you can bookmark the documentation for the Config type definition as a quick reference for later. (Each section features snippets of Rec-000001_config.json but rewritten in Python, as that is the main focus of this wiki. They look similar, but watch out for the differences!)

Experimental Setup

This section contains information that is often used throughout the entire pipeline.

{
    'experimental_setup': {
        'heating_frequency_hertz': 0.1,
        'meters_per_pixel': 2.5e-05,
        'material_properties': {
            'specific_heat_capacity_J__kg_K': 385.0,
            'density_kg__m3': 8960.0,
        },
    },
}

Its fields are described in the ExperimentalSetup documentation, but 'meters_per_pixel' may need more explanation. It refers to the real-world width, in meters, that one pixel of your video covers on the material sample. In other words, it links the resolution of your video to the physical size of what you are filming.

Region Information

In this section, you specify the region or regions you want to crop out of the video.

{
    'region_information': {
        'geometry': {
            'min_x_pixels': 348,
            'max_x_pixels': 521,
            'min_y_pixels': 103,
            'max_y_pixels': 311,
            'heat_source_x_pixels': 558,
        },
        'structure': {
            'average_out_span': True,
            'num_deinterleaving_groups': 5,
        },
    },
}

There are several formats you can use. This snippet specifies a single rectangular region, heated by a line-source from the positive x-direction, that will have its temperatures averaged along the y-axis and then split up again into 5 groups, where each point of temperature is 5 pixels apart from the next point in its group.

The Config documentation somewhat lists all the formats you can use, but the first thing to take note of is that in 'region_information', you can tell analyze_region to extract either one region (such as a rectangular cutout) or multiple regions (such as different sectors of an annulus). You specify just one region by assigning to 'region_information' a dict in the format of RegionConfig, which is what the above snippet does. You can specify multiple regions by using the RegionBatchConfig format or assigning to 'region_information' a list of dicts, which can either be in RegionConfig or RegionBatchConfig format.

Next, RegionConfig and RegionBatchConfig both have a field in which you put geometry (geometry and geometries, respectively). Geometry can either be rectangular (CartesianGeometry) or circular (PolarGeometry). You assign geometry a dict in one of these formats and you assign geometries a list of dicts in these formats. Both region config variants also have a structure field, which gives you a handful of options to manipulate and reorganize your temperatures to be easier to use, all listed under the RegionStructure documentation.

As a word of caution, your results may come out with different groupings in terms of their array dimensions or how they are nested in lists depending on the splitting and aggregation options you use, so you may have to do some inspection to see exactly how your results are structured.

Signal Processor

In this section, you specify exactly how you want to extract amplitude ratios and phase differences from the temperatures you have cropped out.

{
    'signal_processor': {
        'name': 'fft',
        'apply_filter': False,
    },
}

This snippet tells analyze_region to use a fast fourier transform to process the temperature data stream and to not apply any filter preprocessing.

You can use one of the built-in signal processors by finding it in the wiki reference page and assigning the corresponding string to 'name'. You can also find more details about the other fields in the SignalProcessorInformation documentation.

Solver

In this section, you specify your heat model and provide any additional information it would need to be fit to your amplitude ratios and phase differences.

{
    'solver': {
        'name': 'log_lopez-baeza',
        'guesses': {
            'thermal_diffusivity_log10_m2__s': -5,
            'convective_heat_transfer_coefficient_log10_W__m2_K': -2,
        },
        'parameters': {
            'r_meters': 0.00137,
            'length_meters': 0.0408,
        },
    },
}

This snippet tells analyze_region to use a model in which heat propagates linearly across a sample measured in Cartesian coordinates and only allows the log of the unknowns to be varied in fitting. This model also requires constants r and length to be specified for you to use it.

Similar to using built-in signal processors, you can find a model on the Sample Solutions page and then enter its 'name' into this section. Each model takes in initial 'guesses' for each of the unknowns that need to be solved for, as well as constant 'parameters' needed to initialize the model, both of which are described under that model's wiki section.

Fitter

In this section, you specify exactly how you want to fit your heat model to your amplitude ratios and phase differences.

{
    'fitter': {
        'name': 'nelder-mead',
        'parameters': {
            'properties_to_use': 'phase-amplitude',
        },
    },
}

This snippet tells analyze_region to use the Nelder-Mead method to minimize squared error. This built-in fitting method also gives the option to choose which signal properties to use to calculate the error. This snippet chooses to use both amplitude ratios and phase differences.

Following the pattern established in Signal Processor and Solver, you can once again find one of the Built-in Fitting Methods and copy over its 'name'. Some may have 'parameters' that change how they work. You can see what they are and whether they are required or optional on the wiki page.

Optional Arguments

Knowing how to set recording_path and config already allows you to make full use of analyze_recording's analysis capabilities, but analyze_recording also sports a few more parameters that may make it more convenient or helpful to use. They are listed below in order of what may be more relevant to use to what is probably less relevant to most, but it is worth reading about and being aware of all of them anyways.

Return Visualization

analyze_recording returns the final results of its processing as just numbers. If you set return_visualization=True, however, each result object will be paired with a graphic that tries to visualize it with intuitive and detailed graphs. You can see how to use these graphics in our full example in Python.

Recording Cache Path

Directly loading the FLIR camera's CSV files for each run of analyze_recording can be time consuming. To address this, analyze_recording has a feature to save this thermal camera data into a more efficient file format that takes only a fraction of the time to load. To use this feature, set recording_cache_path to a directory in which you would like to save these new files. analyze_recording will save to this directory when processing a recording the first time, and it will try to find the cache file in this directory for every subsequent run.

Memory Cache

As of this repository's latest version, this feature has not been implemented yet.

Debug

analyze_recording will normally try to catch exceptions and return whatever results it has calculated so far (to save you time from rerunning all your analysis), but setting debug=True will allow exceptions to bypass analyze_recording's error handling and show themselves in full. Though, this option is really only helpful for developers.

A Full Example in Python

Now that you are fully acquainted with analyze_recording, here's a script that you can almost basically copy into Python and run as is! (You would still need to have a folder with data and remove the one commented line.) Keep in mind that this uses a different config than what is shown in config's Format, so you can see variations of each section.

import matplotlib.pyplot as plt
from pyangstrom import analyze_recording
from pyangstrom.helpers import calc_thermal_conductivity

results, figures = analyze_recording(
    'C:\\Users\\Name\\Documents\\Rec-000002',
    {
        'experimental_setup': {
            'heating_frequency_hertz': 0.1,
            'meters_per_pixel': 2.5e-05,
            'material_properties': {
                'specific_heat_capacity_J__kg_K': 385.0,
                'density_kg__m3': 8960.0,
            },
        },
        'region_information': [
            {
                'geometries': [
                    {
                        'center': {
                            'x_pixels': 358.0,
                            'y_pixels': 217.0,
                        },
                        'min_r_pixels': 55.0,
                        'max_r_pixels': 105.0,
                        'num_r': 50,
                        'min_theta_degrees': 0.0,
                        'max_theta_degrees': 45.0,
                        'num_theta': 180,
                    },
                    {
                        'center': {
                            'x_pixels': 358.0,
                            'y_pixels': 217.0,
                        },
                        'min_r_pixels': 55.0,
                        'max_r_pixels': 105.0,
                        'num_r': 50,
                        'min_theta_degrees': 45.0,
                        'max_theta_degrees': 90.0,
                        'num_theta': 180,
                    },
                    ... # NOTE: Remaining geometries left out for brevity
                ],
                'structure': {
                    'average_out_span': True,
                    'num_deinterleaving_groups': 5,
                },
                'average_over_regions': True,
            },
        ],
        'signal_processor': {
            'name': 'fft',
            'apply_filter': False,
        },
        'solver': {
            'name': 'log_kil',
            'guesses': {
                'thermal_diffusivity_log10_m2__s': -5,
                'convective_heat_transfer_coefficient_log10_W__m2_K': 1,
            },
            'parameters': {
                'sample_thickness_meters': 0.00158,
                'heating_source_radius_meters': 0.0013,
                'outer_boundary_radius_meters': 0.048,
            },
        },
        'fitter': {
            'name': 'nelder-mead',
            'parameters': {
                'properties_to_use': 'phase-amplitude',
            },
        },
    },
    return_visualization=True,
    recording_cache_path='C:\\Users\\Name\\Documents\\pyangstrom_cache',
)

for r in results:
    k = calc_thermal_conductivity(
        10**r.unknowns_solutions['thermal_diffusivity_log10_m2__s'],
        specific_heat_capacity_J__kg_K=385.0,
        density_kg__m3=8960.0,
    )
    h = 10**r.unknowns_solutions['convective_heat_transfer_coefficient_log10_W__m2_K']
    print(
        f'conductivity {k} m^2/s\n'
        f'coefficient {h} W/m^2K'
    )

plt.show()

Take note of how this example passes config in directly as a dict instead and how 'region_information' uses the list[RegionBatchConfig] format (though, with only one RegionBatchConfig in the list). It also uses the 'log_kil' heat model, which needs different parameters than 'log_lopez-baeza'.

This example also includes optional arguments. We are specifying a directory in which to save thermal video cache files with recording_cache_path. And return_visualization causes analyze_recording to not only return its normal results but to generate visualizations using Matplotlib and pair them up with the results in a tuple. (The actual visualization objects are lists of Figures, arranged in the same way as the lists of result objects.)

If analyze_recording finishes running successfully, results should be a list of FittingResults (More result object types are explained in the Advanced Usage guide). FittingResults have an attribute unknowns_solutions, a dict with the same fields as 'solver''s 'guesses', but with values that have been computed from the actual data. The example loops over each FittingResult, pulling out each value and using them to calculate final numbers that are more understandable. Here, calc_thermal_conductivity is also used, one of the functions provided by pyangstrom for convenience, which can be imported from the pyangstrom.helpers module. Lastly, the example calls plt.show(), which tells Matplotlib to actually show the new visualizations.

And with that, we have gotten through all of Basic Usage! It may have seemed like a lot, but I have no doubt you will eventually come around to needing even more functionality. When that time arrives, please read on with the Advanced Usage guide, where there are juicy details about how to customize PyAngstrom, how it works under the hood, and other "hidden" features not mentioned here. May your thermal analysis experiments run smoothly and advance knowledge for all!

Clone this wiki locally