diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 0a8760b..de93932 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,4 +20,9 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v2.3.0 hooks: - - id: check-yaml \ No newline at end of file + - id: check-yaml +- repo: https://github.com/kynan/nbstripout + rev: 0.5.0 + hooks: + - id: nbstripout + files: ^docs/ \ No newline at end of file diff --git a/README.md b/README.md index b81229f..968ac2e 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,17 @@ Note: Craterpy is not a crater detection algorithm (e.g. [PyCDA](https://github. ## Quickstart -Install with `pip install craterpy` then follow the full worked example in the docs [Getting Started](https://craterpy.readthedocs.io/latest/getting_started.html). +Install with `pip install craterpy` then see example usage at [Getting Started](https://craterpy.readthedocs.io/latest/getting_started.html). ## Demo Quickly import tabluar crater data from a CSV and visualize it on a geotiff in 2 lines of code: ```python -from craterpy import CraterDatabase, sample_data +from craterpy import CraterDatabase, sample_data as sd -cdb = CraterDatabase(sample_data['vesta_craters.csv'], 'Vesta', units='m') -cdb.plot(sample_data['vesta.tif'], alpha=0.5, color='tab:green') +cdb = CraterDatabase(sd['vesta_craters_km.csv'], 'Vesta', units='km') +cdb.plot(sd['vesta.tif'], alpha=0.5, color='tab:green', savefig='readme_vesta_cdb.png') ``` ![Vesta map plot](https://github.com/cjtu/craterpy/raw/main/docs/_images/readme_vesta_cdb.png) @@ -70,8 +70,8 @@ cdb.plot(sample_data['vesta.tif'], alpha=0.5, color='tab:green') Clip and plot targeted regions around each crater from large raster datasets. ```python -cdb.add_circles('crater_rois', 3) -cdb.plot_rois(sample_data['vesta.tif'], 'crater_rois', range(1500, 1503)) +cdb.add_circles('crater_roi', 1.5) +cdb.plot_rois(sd['vesta.tif'], 'crater_roi', range(3, 12)) ``` ![Vesta plot rois](https://github.com/cjtu/craterpy/raw/main/docs/_images/readme_vesta_rois.png) @@ -79,22 +79,28 @@ cdb.plot_rois(sample_data['vesta.tif'], 'crater_rois', range(1500, 1503)) Extract zonal statistics for crater regions of interest. ```python -# Import lunar crater and define the floor and rim -cdb = CraterDatabase(sample_data['moon_craters.csv'], 'Moon', units='km') -cdb.add_annuli("floor", 0.4, 0.8) # Crater floor (exclude central peak and rim) -cdb.add_annuli("rim", 0.9, 1.1) # Thin annulus at crater rim - -# Compute summary statistics for every ROI see docs for supported stats -stats = cdb.get_stats(sample_data['moon_dem.tif'], regions=['floor', 'rim'], stats=['median']) - -# Compute crater depth as rim elevation - floor elevation -stats['depth (m)'] = (stats.median_rim - stats.median_floor) -print(stats.head(3).round(2)) - -# Name Rad Lat Lon median_floor median_rim depth (m) -# Olbers D 50.015 10.23 -78.03 -1452.50 -1322.88 129.62 -# Schuster 50.04 4.44 146.42 445.58 1976.97 1531.39 -# Gilbert 50.125 -3.20 76.16 -2213.66 -731.64 1482.02 +import pandas as pd +from craterpy import CraterDatabase, sample_data as sd +df = df = pd.read_csv(sd["moon_craters_km.csv"]) +cdb = CraterDatabase(df[df["Diameter (km)"] < 60], "Moon", units="km") + +# Define regions for crater floor, rim (sizes in crater radii) +cdb.add_annuli("floor", 0.4, 0.6) # crater floor, excluding possible central peak +cdb.add_annuli("rim", 0.99, 1.01) # thin annulus at rim + +# Pull statistics from a Lunar Digital Elevation Model (DEM) GeoTiff +stats = cdb.get_stats(sd["moon_dem.tif"], regions=['floor', 'rim'], stats=['mean']) + +# Use mean elevations to compute depth (rim to floor) +stats['crater_depth (m)'] = (stats.mean_rim - stats.mean_floor) +print(stats.head().to_string(float_format='%.1f', index=False)) + +# Diameter (km) Latitude Longitude mean_floor mean_rim crater_depth (m) +# 60.0 19.4 -146.5 6070.0 10792.9 4722.9 +# 60.0 44.2 145.3 -976.4 3114.0 4090.4 +# 60.0 -43.6 -7.5 -3617.5 186.8 3804.4 +# 60.0 -9.6 134.7 1843.4 6127.9 4284.4 +# 59.9 -25.3 2.4 -2634.2 -945.0 1689.1 ``` ## Documentation diff --git a/craterpy/classes.py b/craterpy/classes.py index b68f2e4..8383a73 100644 --- a/craterpy/classes.py +++ b/craterpy/classes.py @@ -319,8 +319,7 @@ def plot( Returns ------- - ax : matplotlib.Axes - Original axes, now with data plotted. + ax : matplotlib.Axes, cartopy.mpl.geoaxes.GeoAxes """ ellipsoid = ccrs.CRS(self._crs).ellipsoid globe = ccrs.Globe( diff --git a/craterpy/data/images/mars.tif b/craterpy/data/images/mars.tif index d78fc8e..f8de914 100644 Binary files a/craterpy/data/images/mars.tif and b/craterpy/data/images/mars.tif differ diff --git a/craterpy/data/images/mercury.tif.aux.xml b/craterpy/data/images/mercury.tif.aux.xml deleted file mode 100644 index e89bdde..0000000 --- a/craterpy/data/images/mercury.tif.aux.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - 0.5019607843137255 - 255.4980392156863 - 255 - 0 - 1 - 21|19|23|22|29|30|26|29|39|26|40|42|46|40|44|47|53|42|44|53|64|68|57|53|73|85|46|80|68|69|74|67|83|95|87|81|86|93|94|106|118|102|121|130|144|119|150|153|150|198|201|188|205|216|211|252|268|292|282|335|297|358|349|415|408|461|494|519|562|583|613|656|714|707|703|748|782|845|851|874|888|880|937|941|903|969|945|925|992|913|1038|1015|934|986|901|848|875|850|895|806|792|865|808|790|832|774|701|755|718|760|730|651|658|648|598|609|605|555|556|515|496|479|507|490|489|467|461|447|464|405|368|386|382|331|327|311|323|330|296|290|296|252|267|252|249|240|220|238|213|218|167|175|184|154|166|147|130|148|142|120|135|114|102|102|108|123|87|88|101|96|71|80|61|72|74|57|61|58|46|45|53|46|35|50|48|34|38|42|32|24|36|30|32|27|28|22|36|29|22|23|33|26|23|28|25|25|22|16|18|25|22|17|12|18|14|19|18|11|11|16|17|20|15|15|13|12|17|11|9|7|10|9|7|8|13|3|7|7|1|6|6|7|7|5|6|4|6|2|6|7|6|6|9|9|29 - - - - YES - 1 - 255 - 99.577937247118 - 34.175909975418 - 99.94 - - - diff --git a/craterpy/data/images/moon_dem.tif b/craterpy/data/images/moon_dem.tif index 5249cb4..7f157f0 100644 Binary files a/craterpy/data/images/moon_dem.tif and b/craterpy/data/images/moon_dem.tif differ diff --git a/craterpy/helper.py b/craterpy/helper.py index d0bf85f..24bb49f 100644 --- a/craterpy/helper.py +++ b/craterpy/helper.py @@ -174,8 +174,6 @@ def greatcircdist(lat1, lon1, lat2, lon2, radius): Examples -------- - .. code-block:: doctest - >>> greatcircdist(36.12, -86.67, 33.94, -118.40, 6372.8) 2887.259950607111 @@ -209,8 +207,6 @@ def inglobal(lat, lon): Examples -------- - .. code-block:: doctest - >>> inglobal(0, 0) True >>> inglobal(91, 0) @@ -557,8 +553,6 @@ def findcol(df, names, exact=False): Examples -------- - .. code-block:: doctest - >>> df = pd.DataFrame({'Lat': [10, -20., 80.0], 'Lon': [14, -40.1, 317.2], 'Diam': [2, 12., 23.7]}) @@ -640,8 +634,6 @@ def latlon_to_cartesian(lat, lon, radius): Examples -------- - .. code-block:: doctest - >>> latlon_to_cartesian(0, 0, 1) (1.0, 0.0, 0.0) diff --git a/docs/_images/readme_moon_robbins.png b/docs/_images/readme_moon_robbins.png deleted file mode 100644 index e1ccfad..0000000 Binary files a/docs/_images/readme_moon_robbins.png and /dev/null differ diff --git a/docs/_images/readme_orientale_robbins.png b/docs/_images/readme_orientale_robbins.png new file mode 100644 index 0000000..8a77eff Binary files /dev/null and b/docs/_images/readme_orientale_robbins.png differ diff --git a/docs/_images/readme_orientale_robbins_gt5.png b/docs/_images/readme_orientale_robbins_gt5.png new file mode 100644 index 0000000..2351982 Binary files /dev/null and b/docs/_images/readme_orientale_robbins_gt5.png differ diff --git a/docs/_images/readme_vesta_cdb.png b/docs/_images/readme_vesta_cdb.png index 6e1b76d..c5359e5 100644 Binary files a/docs/_images/readme_vesta_cdb.png and b/docs/_images/readme_vesta_cdb.png differ diff --git a/docs/_images/readme_vesta_rois.png b/docs/_images/readme_vesta_rois.png index f2d54ce..7b40c3d 100644 Binary files a/docs/_images/readme_vesta_rois.png and b/docs/_images/readme_vesta_rois.png differ diff --git a/docs/source/api.md b/docs/source/api.md index df5a656..31d5801 100644 --- a/docs/source/api.md +++ b/docs/source/api.md @@ -1,24 +1,23 @@ # Craterpy API Documentation -## The CraterDatabase +## CraterDatabase ```{eval-rst} .. autoclass:: craterpy.classes.CraterDatabase :members: ``` - +``` diff --git a/docs/source/getting_started.md b/docs/source/getting_started.md index 1305e06..7a39de0 100644 --- a/docs/source/getting_started.md +++ b/docs/source/getting_started.md @@ -1,91 +1,122 @@ # Getting Started -The basis of `craterpy` is the `CraterDatabase` which reads, digitizes, plots, and extracts statistics from supplied crater locations. +The basis of `craterpy` is the `CraterDatabase` which reads crater data, manages coordinate reference systems (CRS), digitizes regions of interest, and plots or extracts statistics from supplied crater locations. -```python -from craterpy import CraterDatabase -cdb = CraterDatabase("/path/to/craters.csv", body="Moon") -``` - -## Importing a list of craters +## Importing Craters -Instantiating a `CraterDatabase` requires a table of craters, the planetary body, and the length units of the radius/diameter column ("m" or "km"). Data can be supplied as a file path or `pandas.DataFrame` that must contain the following named columns in any order (case-insensitive): - -- `Latitude` or `Lat` -- `Longitide` or `Lon` -- `Radius` or `Rad` or `Diameter` or `Diam` - -When multiple columns match, `craterpy` uses the first column found. +The `CraterDatabase` takes a table of craters with at least columns for latitude, longitude, and radius or diameter of the craters. The exact names of the columns are not strict, ex 'Lat' and 'rad' are recognized, but if multiple columns match, `craterpy` will assume the first is correct. ```{note} To choose specific columns or filter to desired rows, first read the table into a `pandas.DataFrame` and pass the filtered subset dataframe to `CraterDatabase`. ``` -Here we read a sample list of lunar craters, filter it to larger than 55 km and import it into a `CraterDatabase`: +Here we read a sample list of lunar craters, optionally filter it to keep only the largest and then improt it into a `CraterDatabase`: ```python -from craterpy import CraterDatabase, sample_data as sd +from craterpy import CraterDatabase, sample_data import pandas as pd -df = pd.read_csv(sd["moon_craters.csv"]) -df = df[df['Rad'] > 55] +crater_file = sample_data["moon_craters_km.csv"] +cdb_all = CraterDatabase(crater_file, body="Moon", units="km") +print(cdb_all) +# Moon CraterDatabase (N=786) + +# Or filter/clean data with pandas first +df = pd.read_csv(sample_data["moon_craters_km.csv"]) +df = df[df["Diameter (km)"] > 100] cdb = CraterDatabase(df, body="Moon", units="km") print(cdb) -# CraterDatabase of length 203 with attributes lat, lon, rad, center. +# Moon CraterDatabase (N=222) ``` -Planetary `body` is required to ensure the correct coordinate reference system (CRS) is used. Supported bodies can be printed with: +Planetary `body` is a required `CraterDatabase` parameter to ensure the correct coordinate reference system (CRS) is used (read more about CRS [here](https://docs.qgis.org/3.40/en/docs/gentle_gis_introduction/coordinate_reference_systems.html)). Supported bodies are given in `craterpy.all_bodies`. The CRS defaults to the `IAU_2015` CRS most commonly used for the particular body, see list [here](https://planetarynames.wr.usgs.gov/TargetCoordinates). A custom input CRS for the crater coordinates can be specified as follows: ```python -import craterpy -print(craterpy.all_bodies) -# ['moon', 'mars', 'mercury', 'venus', 'europa', 'ceres', 'vesta'] +from craterpy import all_bodies, CraterDatabase, sample_data +print(all_bodies) +# ['mercury', 'venus', 'moon', 'earth', 'mars', 'ceres', 'vesta', 'europa', 'ganymede', 'callisto', 'enceladus', 'tethys', 'dione', 'rhea', 'iapetus', 'pluto'] + +custom_crs = "IAU_2015:200000100" # Can be any CRS string recognized by pyproj +cdb = CraterDatabase(sample_data["ceres_craters_km.csv"], "ceres", input_crs=custom_crs, units="km") ``` ```{note} -Crater sizes can be given as radius or diameter, however, the length units should be specified as meters (`m`) or kilometers (`km`). Default is `units="m"`. +Make sure to specify `units="m"` (meters) or `units="km"` (kilometers) for crater size (default is `m`). Size will be determined by the first radius or diameter column in the dataframe with the given units. ``` -## Adding geometries to a CraterDatabase +## Adding regions + +New regions produce shapefile geometries for every crater in the `CraterDatabase`. Circular and annular regions are defined in units of crater radii from the center: + +- `size=1`: 1 radius from the center (the circular crater rim) +- `size=2`: 2 raddii from the center (one radius beyond the rim) +- `size=3`: 3 radii from the center (one diameter beyond the rim) +- etc... -A new geometry can be defined for each crater in the database. Circular and annular geometries have sizes specified in units of number of crater radii from the center of the crater (e.g. `size=1` is a circle at the crater rim, `size=2` is one radius beyond the rim, size=3 is one diameter beyond the rim, etc). Circles enclose all area within `size` radii from each crater. Annuli cut out a circle at the center and enclose the area from `inner` to `outer`. +Circles cover all enclosed area within `size` radii. Annuli are defined from the `inner` to `outer` radius and exclude the inner circle at the center. ```python -# Cicles centeres on each crater +from craterpy import CraterDatabase, sample_data as sd +import matplotlib.pyplot as plt +import cartopy.crs as ccrs +cdb = CraterDatabase(sd["moon_craters_km.csv"], body="Moon", units="km") + +# Default plot is circular ROI at 1 crater radius +cdb.plot() + +# Explicitly add circular ROIs and plot on a raster cdb.add_circles("crater", size=1) -# Annulus from rim to 1 crater diameter past the rim (3 radii from the center) -cdb.add_annuli("ejecta", inner=1, outer=3) +cdb.plot(sd["moon.tif"], "crater") + +# Annular ROIs with some plot customization +cdb.add_annuli("ejecta", inner=1, outer=3) +ax = cdb.plot(sd["moon.tif"], "ejecta", linestyle="--", color="tab:green", alpha=0.4, size=8, dpi=200) +ax.set_ylim(-30, 70) +ax.set_xlim(-90, 90) +plt.title("New title here") +plt.show() ``` -Geometries are stored under the supplied name as `geopandas.GeoSeries` objects. These geometries contain map projection information and can be saved and read from a GeoJSON shapefile with `.to_geojson()`, a fomat that is widely recognized by most GIS applications. +## Plotting ROIs -```python -# Save to shapefile -cdb.to_geojson("lunar_craters.geojson", "crater") -# Read from shapefile -new_cdb = CraterDatabase("lunar_craters.geojson", body="Moon", units="km") -``` +In addition to plotting the full `CraterDatabase` as above, individual crater ROIs can be shown if a georeferenced raster image is given. -Geometries can be shown on a plot with with or without an image (images should be converted to a georeferenced raster format before use with `craterpy`). ```python -cdb.plot() -# +from craterpy import CraterDatabase, sample_data as sd +cdb = CraterDatabase(sd["tethys_craters_km.csv"], body="tethys", units="km") -cdb.plot(sd["moon.tif"]) -# +cdb.add_circles("crater", size=2) +cindex = cdb.data.iloc[10:19].index +cdb.plot_rois(sd["tethys.tif"], "crater", range(1,10)) -# Plot the craters on a larger image with higher resoltion -cdb.plot(sd["moon.tif"], "crater", size=10, dpi=200) +cdb.add_annuli("ejecta", 1, 2) +cdb.plot_rois(sd["tethys.tif"], "ejecta", range(1,10), color='black', cmap="cividis", grid_kw={'alpha': 0}) ``` -Individual regions of interest ROIs can also be shown as an array of subplots. + +```{note} +See other sample planetary crater data that can be plotted with `craterpy` in {doc}`planetary_body_examples`. +``` + +## Exporting ROIs to GIS + +Regions of interest can be saved as GeoJSON shapefiles for use in any GIS application (e.g. ArcGIS or QGIS) using `.to_geojson()`. ```python -cdb.plot_rois(sd["moon.tif"], "crater") -# +from craterpy import CraterDatabase, sample_data as sd + +cdb = CraterDatabase(sd["mars_craters_km.csv"], body="Mars", units="km") +cdb.add_circles("crater", size=1) +print(cdb) +# Mars CraterDatabase (N=352) -cdb.add_annuli("rim", 0.5, 1.56) -cdb.plot_rois(sd["moon.tif"], "rim", color='black', cmap="cividis", grid_kw={'alpha': 0}) +# Save to shapefile +cdb.to_geojson("mars_craters.geojson", "crater") + +# Read from shapefile +new_cdb = CraterDatabase("mars_craters.geojson", body="Mars", units="km") +print(new_cdb) +# Mars CraterDatabase (N=352) ``` ## Extracting statistics @@ -93,38 +124,69 @@ cdb.plot_rois(sd["moon.tif"], "rim", color='black', cmap="cividis", grid_kw={'al Stats can be computed from the crater geometries from a supplied georeferenced raster with: ```python -cdb = CraterDatabase(sd["moon_craters.csv"], "Moon", units="m") +from craterpy import CraterDatabase, sample_data as sd +import pandas as pd -# Define regions for central peak, crater floor, and rim (sizes in crater radii) -cdb.add_annuli("peak", 0, 0.1) -cdb.add_annuli("floor", 0.3, 0.6) -cdb.add_annuli("rim", 1.0, 1.2) +df = pd.read_csv(sd["moon_craters_km.csv"]) +cdb = CraterDatabase(df[df["Diameter (km)"] > 20], "Moon", units="km") -stats = cdb.get_stats(sd["moon.tif"], regions=['floor', 'peak', 'rim'], stats=['mean', 'std']) +# Define regions for crater floor, rim, and ejecta (sizes in crater radii) +cdb.add_annuli("floor", 0.4, 0.6) # crater floor, excluding possible central peak +cdb.add_annuli("rim", 0.99, 1.01) # thin annulus at rim +cdb.add_annuli("background", 5, 5.5) # outside the continuous ejecta + +# Pull statistics from Lunar Digital Elevation Model (DEM) +stats = cdb.get_stats(sd["moon_dem.tif"], regions=['floor', 'rim', 'background'], stats=['mean']) + +# Use mean elevations to compute depth (rim to floor) and height of rim above background +stats['crater_depth'] = (stats.mean_rim - stats.mean_floor) +stats['rim_height'] = (stats.mean_rim - stats.mean_background) print(stats.head()) +ax = stats[["crater_depth", "rim_height"]].plot(kind="hist", bins=100, alpha=0.8) +ax.set_xlabel('Height (m)') +# Expect depths to be greater than rim height ``` For a list of valid statistics, see the [rasterstats documentation](https://pythonhosted.org/rasterstats/manual.html#zonal-statistics). The input raster file must be georeferenced and importable by `gdal`. -## All craters on the moon > 2km +## Example: Plot all lunar craters near Orientale Basin + +First download the [Lunar Crater Database](https://pdsimage2.wr.usgs.gov/Individual_Investigations/moon_lro.kaguya_multi_craterdatabase_robbins_2018/data/) (Robbins, 2018) in CSV format. Currently `craterpy` works on any tabular data with a diam, lat, and lon column. Since crater databases are often not supplied in a standardized and labelled PDS format, some manual data cleaning may be necessary to ensure the correct columns, planetary body, and units are used. -To plot all > 2km craters on the moon, first download the [Lunar Crater Database](https://pdsimage2.wr.usgs.gov/Individual_Investigations/moon_lro.kaguya_multi_craterdatabase_robbins_2018/data/) (Robbins, 2018) in CSV format. +Here we read the data with `pandas` and filter to the desired columns and area. We rename the diam, lat, and lon columns for convenience, then pass only those columns to `CraterDatabase` to ensure `craterpy` uses the correct values (the Robbins CSV contains mulitple columns corresponding to each). Pre-filtering the region desired saves on computation time. -Then run the following code to read in, filter, and ingest the database as a `CraterDatabase`, and then visualize it. **Note:** visualization of so many craters may take several minutes, or even tens of minutes depending on the machine, to appear. +Here, taking only craters larger than 5km results in ~12500 craters vs. all craters larger than 1 km results in ~175k craters. On `craterpy v0.9.5` these took ~20s and ~5 minutes to plot, respectively (when tested on a Ryzen 7 laptop with 8 cores at 1.9 GHz and 24GB RAM). ```python -from craterpy import CraterDatabase +from craterpy import CraterDatabase, sample_data as sd import pandas as pd -df = pd.read_csv('lunar_crater_database_robbins_2018.csv') -df = df[df['DIAM_CIRC_IMG'] > 2] # Filter out craters < 2 km diameter +df = pd.read_csv('/home/cjtu/projects/craterpy/tmp/lunar_crater_database_robbins_2018.csv') +df = df.rename(columns={'DIAM_CIRC_IMG':'diam', 'LAT_CIRC_IMG':'lat', 'LON_CIRC_IMG':'lon'}) +df = df[['diam', 'lat', 'lon']] +df = df[(-70 < df.lat) & (df.lat < 30) + & (220 < df.lon) & (df.lon < 320) + & (1 < df.diam) & (df.diam < 300)] + +# Subset to only 5 km and larger craters, plot on DEM +df5 = df[(5 < df.diam)] +cdb5 = CraterDatabase(df5, "Moon", units="km") +cdb5.plot(sd['moon_dem.tif'], alpha=0.3, color='k', savefig='readme_orientale_robbins_gt5.png') +``` + +![Lunar craters plot](https://github.com/cjtu/craterpy/raw/main/docs/_images/readme_orientale_robbins_gt5.png) + + +```python +# Plot all craters >1km, this may take some time! cdb = CraterDatabase(df, "Moon", units="km") -print('Plotting... (this may take several minutes)') -cdb.plot(linewidth=0.25, alpha=0.25, color='gray') +cdb.plot(sd['moon_dem.tif'], alpha=0.3, color='k', savefig='readme_orientale_robbins.png') ``` -![Lunar craters plot](https://github.com/cjtu/craterpy/raw/main/docs/_images/readme_moon_robbins.png) +![Lunar craters plot](https://github.com/cjtu/craterpy/raw/main/docs/_images/readme_orientale_robbins.png) + +For more plotting customization options using `cartopy`, see the this page in their [docs](https://scitools.org.uk/cartopy/docs/v0.13/matplotlib/intro.html). ## More help diff --git a/docs/source/planetary_body_examples.ipynb b/docs/source/planetary_body_examples.ipynb index 255fd87..bd0e528 100644 --- a/docs/source/planetary_body_examples.ipynb +++ b/docs/source/planetary_body_examples.ipynb @@ -59,6 +59,14 @@ " ax = cdb.plot_rois(sample_data[f'{body}.tif'], \"Ejecta\")\n", " plt.show()\n" ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4649e500", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": {