This page was generated from docs/examples/mineralML_interactive.ipynb. Interactive online version: Binder badge.

Python Notebook Download

[1]:
""" Created on April 24, 2026 // @author: Sarah Shi """

import numpy as np
import sys
sys.path.append('../../src/')
import mineralML as mm

import matplotlib.pyplot as plt
%matplotlib inline
%config InlineBackend.figure_format = 'png'

Interactive Tools for Mapped EDS Data

Open In Colab

This notebook demonstrates interactive tools for exploring mineralML phase maps. These tools require a live Python kernel and an interactive Matplotlib backend — they cannot be run on ReadTheDocs. Use the Colab badge above or run locally with %matplotlib widget (requires ipympl).

pip install ipympl

The tools shown here are:

  • ``mm.interactive_pixels`` — click pixels to sample oxide compositions

  • ``mm.interactive_line_profile`` — click to draw transects and extract line profiles

  • ``mm.plot_locations`` — plot the locations of picked pixels or transects on a map

1. Load and run a map

First, run mm.run_map as usual to get the result dictionary. See the EDS mapping notebook for full details on loading and preparing data.

[2]:
%matplotlib inline

result = mm.run_map(
    "Maps/09g3",
    renormalize=True,
    units="element_wt%",
    pixel_size_um=4.0,
    scalebar_um=50,
    total_threshold=50,
    min_frac=0.01,
)
[ok] Mg Wt%Montaged Map Data.csv  →  Mg  (329, 283)
[ok] Ca Wt%Montaged Map Data.csv  →  Ca  (329, 283)
[ok] Al Wt%Montaged Map Data.csv  →  Al  (329, 283)
[ok] Fe Wt%Montaged Map Data.csv  →  Fe  (329, 283)
[ok] Na Wt%Montaged Map Data.csv  →  Na  (329, 283)
[ok] Mn Wt%Montaged Map Data.csv  →  Mn  (329, 283)
[ok] Cr Wt%Montaged Map Data.csv  →  Cr  (329, 283)
[ok] Ti Wt%Montaged Map Data.csv  →  Ti  (329, 283)
[ok] Si Wt%Montaged Map Data.csv  →  Si  (329, 283)
[ok] K Wt%Montaged Map Data.csv  →  K  (329, 283)
mineralML: 93107 rows — 57732 classified by neural network, 0 by empirical rules (Zircon: 0, SiO2 polymorph: 0, Carbonate: 0), 35375 skipped (invalid/empty)
prep_df: 57732 row(s) processed (of 57732 input, 0 dropped).
/Users/sarahshi/Documents/GitHub/mineralML/docs/examples/../../src/mineralML/hybrid.py:280: UserWarning: The column 'Mineral' was missing and has been filled with NaN.
  df = prep_df(df)
/Users/sarahshi/Documents/GitHub/mineralML/docs/examples/../../src/mineralML/hybrid.py:280: UserWarning: Non-numeric values in 57732 row(s) were coerced to NaN.
  df = prep_df(df)
../_images/examples_mineralML_interactive_3_2.png
../_images/examples_mineralML_interactive_3_3.png
../_images/examples_mineralML_interactive_3_4.png

2. Interactive pixel composition picker

mm.interactive_pixels displays the phase map and records the full oxide composition of each pixel you click. Each click:

  • Places a marker on the map

  • Prints the oxide values below the figure

  • Appends a row to the dataframe in controller["picks"]

Keybindings: r/u undo last pick  |  c clear all  |  q/Esc quit

After quitting, access your picks as a DataFrame:

controller["picks"]
[3]:
%matplotlib widget

controller = mm.interactive_pixels(result,
                                   region=3, # side length of square averaging box (1 = single pixel; use odd integers, e.g. 3, 5)
                                   phase=None, # specify phase to display (e.g. "plagioclase"); if None, will display all pixels regardless of phase assignment
                                   )
[4]:
%matplotlib inline

controller["picks"]
[4]:
x y phase n_pixels SiO2 TiO2 Al2O3 FeOt MnO MgO CaO Na2O K2O Cr2O3 Total Total_raw
0 24 74 Orthopyroxene 9 51.382158 0.231275 5.935667 14.527422 0.092448 26.585354 0.838933 0.338983 -0.013140 0.080898 100.0 99.452320
1 14 85 Orthopyroxene 9 51.755858 0.266427 6.422178 13.811186 0.208105 26.407858 0.979675 0.164893 -0.041242 0.025062 100.0 99.796230
2 41 107 Plagioclase 9 51.243466 0.164586 31.054675 0.062434 -0.078979 -0.104385 13.301734 4.251682 0.276118 -0.171331 100.0 98.990366
3 38 128 Plagioclase 9 52.196867 -0.024729 30.320998 0.405566 -0.004646 0.046737 12.551467 4.307204 0.332112 -0.131575 100.0 98.874299
4 95 232 Clinopyroxene 9 49.447336 0.722313 8.024595 6.979877 0.101082 13.946927 19.894387 0.797412 0.256247 -0.170175 100.0 97.394634
5 103 240 Clinopyroxene 9 49.604274 0.561800 8.036716 6.500822 0.003462 13.893682 20.402058 0.840622 0.130585 0.025979 100.0 99.288592
6 168 283 Oxide 9 -0.048897 0.267502 59.991736 22.327466 -0.148192 15.930729 0.118597 0.117541 0.018457 1.425061 100.0 97.400370
7 173 283 Oxide 9 -0.249181 0.119954 60.926417 21.555273 -0.020259 16.497383 0.043414 -0.083181 -0.017739 1.227919 100.0 98.294721

2a. Filter to a specific phase

Pass phase to restrict the display and clicks to one or more mineral phases. Pixels outside the selected phase are shown as white background.

[5]:
%matplotlib widget

controller_opx = mm.interactive_pixels(result,
                                       region=3, # side length of square averaging box (1 = single pixel; use odd integers, e.g. 3, 5)
                                       phase="Plagioclase" # plot only pixels classified as plagioclase (optional; if None, will display all pixels regardless of phase assignment
                                       )

2b. Display an oxide heatmap as background

Pass oxide_key to show an oxide or component concentration map instead of the phase map. Combine with phase to restrict to a single mineral.

[6]:
%matplotlib widget

controller_sio2 = mm.interactive_pixels(result,
                                        region=3, # side length of square averaging box (1 = single pixel; use odd integers, e.g. 3, 5)
                                        oxide_key="Feldspar.An", # plot An content of each pixel instead of phase assignment
                                        phase="Plagioclase" # plot only pixels classified as plagioclase (optional; if None, will display all pixels regardless of phase assignment
                                        )

3. Plot pick locations

mm.plot_locations overlays picked pixel locations on a map. Pass map_key to use an oxide map as the background.

[7]:
%matplotlib inline

fig, ax = mm.plot_locations(result,
                            controller["picks"], # plot only the pixels that have been picked
                            map_key="Na2O", # color the points by their Na2O content
                            )
../_images/examples_mineralML_interactive_12_0.png

4. Interactive line profiles

mm.interactive_line_profile lets you draw transects across a map by clicking a start and end point. Each completed transect is extracted and plotted immediately.

Keybindings: r reset current clicks  |  u undo last transect  |  c clear all  |  q/Esc quit

Access results after quitting:

lp["profiles_df"]     # all profile data concatenated
lp["coordinates_df"]  # transect start/end coordinates
[8]:
%matplotlib widget

lp = mm.interactive_line_profile(
    result,
    key="SiO2", # key of the oxide map to plot the line profile for
    method="none", # do not apply any smoothing to the line profile. use mean or median (across the pixel width) for a smoothed profile
    width_px=5, # width of the line profile in pixels
    pixel_size_um=4.0, # pixel size in micrometers
)

4a. Filter transects to a single phase

Pass phase to mask the background map to pixels of a specific mineral before drawing transects.

[9]:
%matplotlib widget

lp_opx = mm.interactive_line_profile(
    result,
    key="Feldspar.An", # plot the Mg# of pixels classified as plagioclase
    phase="Plagioclase",
    method="none",
    width_px=5,
    pixel_size_um=4.0,
)

Return the line profile dataframe.

[10]:
%matplotlib inline

lp["profiles_df"]
[10]:
profile_id distance_px distance_um SiO2 SiO2_smoothed x y perp_distance_px source width_px length_px length_um color x0 y0 x1 y1
0 1 0.029312 0.117246 49.490084 49.490084 46.0 80.0 0.452090 auto 5.0 72.893565 291.574258 #1f77b4 46 80 30 151
1 1 0.254171 1.016684 48.257454 48.257454 45.0 80.0 1.426481 auto 5.0 72.893565 291.574258 #1f77b4 46 80 30 151
2 1 0.479031 1.916122 50.015794 50.015794 44.0 80.0 2.400872 auto 5.0 72.893565 291.574258 #1f77b4 46 80 30 151
3 1 0.553984 2.215935 40.963464 40.963464 48.0 81.0 -1.721552 auto 5.0 72.893565 291.574258 #1f77b4 46 80 30 151
4 1 0.778843 3.115373 53.601392 53.601392 47.0 81.0 -0.747161 auto 5.0 72.893565 291.574258 #1f77b4 46 80 30 151
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
750 3 36.333159 145.332637 51.627654 51.627654 172.0 233.0 1.929236 auto 5.0 36.435405 145.741620 #2ca02c 173 197 174 233
751 3 36.358151 145.432606 60.081533 60.081533 173.0 233.0 0.929548 auto 5.0 36.435405 145.741620 #2ca02c 173 197 174 233
752 3 36.383144 145.532574 56.881043 56.881043 174.0 233.0 -0.070139 auto 5.0 36.435405 145.741620 #2ca02c 173 197 174 233
753 3 36.408136 145.632543 49.347756 49.347756 175.0 233.0 -1.069827 auto 5.0 36.435405 145.741620 #2ca02c 173 197 174 233
754 3 36.433128 145.732512 52.350107 52.350107 176.0 233.0 -2.069515 auto 5.0 36.435405 145.741620 #2ca02c 173 197 174 233

755 rows × 17 columns

5. Plot transect locations

Pass the coordinates_df from a line profile controller to mm.plot_locations to visualize transect positions.

[11]:
%matplotlib inline

fig, ax = mm.plot_locations(result,
                            lp["coordinates_df"],
                            map_key="Na2O"
                            )
../_images/examples_mineralML_interactive_20_0.png

6. Batch extract profiles from saved transects

mm.batch_extract_line_profiles extracts profiles for multiple oxides at once from a saved transect table. Pass lp["coordinates_df"] from an interactive session, or any DataFrame with x0, y0, x1, y1 columns.

Each oxide becomes its own column, making it easy to compare compositions along the same transect.

[12]:
%matplotlib inline

profiles_df = mm.batch_extract_line_profiles(
    result,
    transects=lp["coordinates_df"],
    keys=["SiO2", "Al2O3", "FeOt", "MgO", "CaO", "Feldspar.An"],
    method="none",
    pixel_size_um=4.0,
)

profiles_df
[12]:
profile_id x y distance_px perp_distance_px distance_um SiO2 SiO2_smoothed Al2O3 Al2O3_smoothed ... CaO CaO_smoothed Feldspar.An Feldspar.An_smoothed x0 y0 x1 y1 width_px source
0 1 46.0 80.0 0.000000 0.000000e+00 0.000000 49.490084 49.490084 7.699435 7.699435 ... 21.724662 21.724662 NaN NaN 46.0 80.0 30.0 151.0 5.0 auto
1 1 45.0 80.0 0.219839 9.755361e-01 0.879357 48.257454 48.257454 8.595989 8.595989 ... 19.916674 19.916674 NaN NaN 46.0 80.0 30.0 151.0 5.0 auto
2 1 44.0 80.0 0.439678 1.951072e+00 1.758713 50.015794 50.015794 8.705449 8.705449 ... 20.377495 20.377495 NaN NaN 46.0 80.0 30.0 151.0 5.0 auto
3 1 48.0 81.0 0.535858 -2.170911e+00 2.143432 40.963464 40.963464 8.341098 8.341098 ... 2.822591 2.822591 NaN NaN 46.0 80.0 30.0 151.0 5.0 auto
4 1 47.0 81.0 0.755697 -1.195375e+00 3.022788 53.601392 53.601392 28.690880 28.690880 ... 10.621687 10.621687 0.547575 0.547575 46.0 80.0 30.0 151.0 5.0 auto
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
746 3 175.0 232.0 35.042039 -1.027381e+00 140.168155 50.870222 50.870222 32.378985 32.378985 ... 13.190178 13.190178 0.675179 0.675179 173.0 197.0 174.0 233.0 5.0 auto
747 3 176.0 232.0 35.069806 -2.026996e+00 140.279224 52.350337 52.350337 30.086376 30.086376 ... 13.510627 13.510627 0.654615 0.654615 173.0 197.0 174.0 233.0 5.0 auto
748 3 172.0 233.0 35.958352 1.999229e+00 143.833408 51.627654 51.627654 30.454367 30.454367 ... 10.819728 10.819728 0.499965 0.499965 173.0 197.0 174.0 233.0 5.0 auto
749 3 173.0 233.0 35.986119 9.996144e-01 143.944477 60.081533 60.081533 22.631185 22.631185 ... 6.053288 6.053288 NaN NaN 173.0 197.0 174.0 233.0 5.0 auto
750 3 174.0 233.0 36.013886 -5.551115e-17 144.055545 56.881043 56.881043 25.929915 25.929915 ... 8.147010 8.147010 0.367105 0.367105 173.0 197.0 174.0 233.0 5.0 auto

751 rows × 24 columns

[13]:
# Long format — one row per oxide per distance bin, useful for plotting
profiles_df, profiles_long_df = mm.batch_extract_line_profiles(
    result,
    transects=lp["coordinates_df"],
    keys=["SiO2", "MgO", "CaO"],
    method="mean",
    pixel_size_um=4.0,
    return_long=True,
)

profiles_long_df
[13]:
bin distance_px value n_pixels distance_um value_smoothed profile_id key source x0 y0 x1 y1 width_px
0 0 0.099427 49.490084 1 0.397708 49.490084 1 SiO2 auto 46.0 80.0 30.0 151.0 5.0
1 1 0.298281 48.257454 1 1.193123 48.257454 1 SiO2 auto 46.0 80.0 30.0 151.0 5.0
2 2 0.497135 45.489629 2 1.988538 45.489629 1 SiO2 auto 46.0 80.0 30.0 151.0 5.0
3 3 0.695988 53.601392 1 2.783953 53.601392 1 SiO2 auto 46.0 80.0 30.0 151.0 5.0
4 4 0.894842 50.561281 1 3.579368 50.561281 1 SiO2 auto 46.0 80.0 30.0 151.0 5.0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
2260 180 35.137873 13.350402 2 140.551491 13.350402 3 CaO auto 173.0 197.0 174.0 233.0 5.0
2261 181 35.332542 NaN 0 141.330170 NaN 3 CaO auto 173.0 197.0 174.0 233.0 5.0
2262 182 35.527212 NaN 0 142.108848 NaN 3 CaO auto 173.0 197.0 174.0 233.0 5.0
2263 183 35.721882 NaN 0 142.887527 NaN 3 CaO auto 173.0 197.0 174.0 233.0 5.0
2264 184 35.916551 8.340008 3 143.666206 8.340008 3 CaO auto 173.0 197.0 174.0 233.0 5.0

2265 rows × 14 columns

7. Composite component map

mm.plot_component_composite overlays continuous solid-solution compositions (e.g. plagioclase An%, olivine Fo%, pyroxene XMg) on top of a categorical phase map. This gives a single figure showing both phase identity and mineral chemistry.

The component maps are computed automatically by run_map and stored in result["component_maps"].

[14]:
%matplotlib inline
# You can restrict which phases appear and adjust the color limits. `limits_mode="std"` clips the colorbar to the 2 sigma range of each component, which is useful when a few extreme pixels would otherwise compress the color scale.

fig, mineral_map, comp_maps = mm.plot_component_composite(
    result,
    title="09g3 Composite",
    pixel_size_um=4.0,
    scalebar_um=50,
    limits_mode="std", # 2 sigma range for color limits
)
../_images/examples_mineralML_interactive_25_0.png
[ ]: