diff --git a/.gitignore b/.gitignore
index 28c4be04c12439804e974fb46909234c6dc182f0..59e33e872cbef74f63048ebfe91ed3d97bf07795 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,6 +11,8 @@ learning-vscode/VSCode_WSL.html
 learning-vscode/VSCode_Ubuntu.html
 learning-markdown/MarkDown.html
 learning-markdown/TODO_makdown.md
+learning-python/Python.html
+learning-python/sandbox.txt
 
 # ---> VisualStudioCode
 .settings
diff --git a/learning-python/Python.md b/learning-python/Python.md
new file mode 100644
index 0000000000000000000000000000000000000000..6cad8495a9eca2e0743e28f8db68167c8dab6c82
--- /dev/null
+++ b/learning-python/Python.md
@@ -0,0 +1,1307 @@
+---
+marp: true
+title: Introduction to Python
+author: P.Y. Barriat
+description: Introduction to Python
+backgroundImage: url('assets/back.png')
+_backgroundImage: url('assets/garde.png')
+footer: 16/10/2024 | Introduction to Python
+_footer: ""
+paginate: true
+_paginate: false
+---
+
+Introduction to Python<!--fit-->
+===
+
+https://forge.uclouvain.be/barriat/learning-python
+
+![h:200](assets/python_logo.png)
+
+##### October 16, 2024
+
+###### ELIC Training Sessions
+
+
+---
+
+# Programming basics
+
+* **code or source code**: The sequence of instructions in a program.
+* **syntax**: The set of legal structures and commands that can be
+used in a particular programming language.
+* **output**: The messages printed to the user by a program.
+* **console**: The text box onto which output is printed.
+
+---
+
+# Compiling and interpreting
+
+Many languages require you to compile (translate) your program into a form that the machine understands.
+
+```mermaid
+graph LR;
+    A[source code: Hello.f90] -->|compile| B(byte code: Hello.exe)
+    B -->|execute| C(output)
+```
+
+Python is instead directly interpreted into machine instructions.
+
+```mermaid
+graph LR;
+    A[code: Hello.py] -->|interpret| B(output)
+```
+
+---
+
+# Python: Overview
+
+* Python is an interpreted language
+* The interpreter provides an interactive environment to play with the language 
+* Simple syntax, relatively easy to learn
+* Useful in many areas (science, web development, GUI programming)
+* Big standard library, many additional packages
+
+* Python 3: new minor version (e.g. 3.9) released every October
+* Python 2: support ended in 2019, [10% of developers](https://www.jetbrains.com/lp/python-developers-survey-2019/) were still using it
+
+---
+
+# Code examples
+
+```py
+>>> 3 + 7
+10
+>>> 3 < 15
+True
+>>> 'print me'
+'print me'
+>>> print('print me')
+print me
+>>> # this is a comment
+>>> a = 3
+>>> b = 4
+>>> a * b
+12
+```
+
+> Results of expressions are printed on the screen
+
+---
+
+# Running Python code
+
+- write a program as a file (or collection of files), run that program 
+  > GUI applications, web applications, data processing pipelines
+- type code into an interactive console or notebook line by line 
+  > for quick calculations, experimenting, data exploration / analysis)
+
+### Using Python
+
+- local installation
+- remote Python server
+- online Python consoles or online Notebooks (Jupyter)
+  > https://www.python.org/shell/
+  > https://jupyterhub.cism.ucl.ac.be
+
+---
+
+# Expressions
+
+* **expression**: a data value or set of operations to compute a value.
+  > `1 + 4 * 3`
+* Arithmetic operators we will use:
+  - `+ - * /` addition, subtraction/negation, multiplication, division
+  - `%` modulus, a.k.a. remainder
+  - `**` exponentiation
+* **precedence**: order in which operations are computed.
+  - `/ % **` have a higher precedence than `+ -`
+    > `1 + 3 * 4` is `13`
+  - Parentheses can be used to force a certain order of evaluation.
+    > `(1 + 3) * 4` is `16`
+
+---
+
+# Variables
+
+- **Variable**: a named piece of memory that can store a value.
+  - Compute an expression's result,
+  - store that result into a variable,
+  - and use that variable later in the program.
+- **Assignment statement**: stores a value into a variable 
+  - Syntax: `name = value`
+  - Examples: `x = 5` , `gpa = 3.14`
+  - A variable that has been given a value can be used in expressions. 
+    `x + 4` is `9`
+
+---
+
+- Names of variables are usually written in lower case, separating words by underscores
+
+```py
+birth_year = 1970
+current_year = 2020
+age = current_year - birth_year
+```
+
+- Variable names may only consist of letters, digits and underscores
+
+- Overwriting (reassigning) variables:
+
+```py
+name = "John"
+name = "Jane"
+a = 3
+a = a + 1
+```
+
+---
+
+# Basic (primitive) data types
+
+- `int` (integer)
+- `float` (floating point number)
+- `str` (string): text
+- `bool` (boolean): yes / no
+- none: missing / unknown value
+
+---
+
+Strings can be enclosed in single or double quotes
+
+```py
+greeting = "Hello"
+name = 'John'
+```
+
+Inserting a variable (f-strings):
+
+```py
+message1 = f"Hello, {name}!"
+```
+
+Joining strings:
+
+```py
+message2 = "Hello, " + name + "!"
+```
+
+---
+
+Strings - escape sequences
+
+```py
+text = "He said: \"hi!\""
+```
+
+Line break: `\n`
+
+```py
+a = 'line 1\nline 2'
+```
+
+single Backslash: `\\`
+
+```py
+b = 'C:\\docs'
+```
+
+---
+
+**boolean** value: yes/no
+
+In Python: `True` or `False`
+
+> note the capitalization
+
+**None** represents a value that is unknown or missing
+
+```py
+first_name = "John"
+middle_name = None
+last_name = "Doe"
+```
+
+---
+
+# Integer division
+
+* When we divide integers with `/` , the quotient is also an integer.
+  - `35 / 5` is `7`
+  - `218 / 5` is `43`
+  - `156 / 100` is `1`
+
+* The `%` operator computes the remainder from a division of integers.
+  - `218 % 5` is `3`
+  - `84 % 10` is `0`
+
+---
+
+# Real numbers
+
+- Python can also manipulate real numbers.
+  > `6.022` , `-15.9997` , `42.0` , `2.143e17`
+- The operators `+ - * / % ** ( )` all work for real numbers.
+  - The `/` produces an exact answer: `15.0/2.0` is `7.5`
+  - The same rules of precedence also apply to real numbers: Evaluate `( )` before `* / %` before `+ -`
+- When integers and reals are mixed, the result is a real number.
+  - Example: `1/2.0` is `0.5`
+  - The conversion occurs on a per-operator basis.
+
+---
+
+# Types and type conversions
+
+Determining the type of a variable via `type`:
+
+```py
+a = 4 / 2
+
+type(a)
+```
+Objects may be converted to other types via `int()`, `float()`, `str()`, `bool()`, ...
+
+```py
+pi = 3.1415
+pi_int = int(pi)
+message = "Pi is approximately " + str(pi_int)
+```
+
+---
+
+- `int(x)` converts `x` to an integer
+- `float(x)` converts `x` to a floating point
+- The interpreter shows a lot of digits
+
+```
+>>> 1.23232
+1.2323200000000001
+>>> print 1.23232
+1.23232
+>>> 1.3E7
+13000000.0
+>>> int(2.0)
+2
+>>> float(2)
+2.0
+```
+---
+
+# Functions
+
+A function is a "sub-program" that can perform a specific task
+
+Examples of predefined functions:
+
+- `len()` can determine the length of a string (or of a list, ...)
+- `id()` can determine the internal ID of an object
+- `type()` can tell us the type of an object
+- `print()` can write some output into the terminal
+- ...
+
+---
+
+- A **function** can receive so-called _parameters_ and produce a result 
+(a _return value_)
+
+  - `len()` can take a string as a _parameter_ and produce an int as the _return value_
+  - `print()` can take various objects as _parameters_; it does **not** have an explicit _return value_
+
+- A **method** is a function that belongs to a specific object type (e.g. to _str_)
+
+  Examples of **string methods**:
+
+  - `first_name.upper()`
+  - `first_name.count("a")`
+  - `first_name.replace("a", "@")`
+
+---
+
+# Builtins, standard library
+
+- **Builtins**: functions and objects that are used frequently and are available at all times
+- **Standard library**: collections of additional modules and packages that can be imported
+
+  > Documentation: https://docs.python.org/3/library/index.html
+
+## Builtins
+
+Amongst others: `print()`, `input()`, `len()`, `open()`, etc
+
+---
+
+## Standard library
+
+The standard library contains additional modules that can be imported.
+
+Example:
+
+```py
+import math
+
+print(math.floor(3.6))
+```
+
+or
+
+```py
+from math import floor
+
+print(floor(3.6))
+```
+
+---
+
+### Math functions
+
+Python has useful functions for performing calculations.
+
+|  Function name    |     Description   |
+| ------------ | --------------- |
+|  `ceil(value)` |  rounds up |
+|  `floor(value)` |  rounds down |
+|  `log(value)` |  logarithm, base e |
+|  `cos(value)` |  cosine, in radians |
+|  `sqrt(value)` |  square root |
+
+etc...
+
+---
+
+### Math constants
+
+|  Constant    |     Description   |
+| ------------ | --------------- |
+|  `e` |  2.7182818... |
+|  `pi` |  3.1415926... |
+
+ To use many of these above, you can write the following at the top of your Python program:
+
+```python
+from math import *
+```
+
+---
+
+# Text input/output
+
+- `input` reads a number from user input.
+  You can assign (store) the result of input into a variable.
+
+- `print` produces text output on the console.
+
+  Prints the given text message (or expression value) on the console, and moves the cursor down to the next line:
+  - `print "Message"` , `print Expression`
+
+  Prints several messages and/or expressions on the same line:
+  - `print Item1, Item2, ..., ItemN`
+
+> A comma at the end will not print a newline character: `print 'hello',`
+
+---
+
+# Examples
+
+```
+>>> x = 7
+>>> x
+7
+>>> x+7
+14
+>>> x = 'hello'
+>>> x
+'hello'
+>>> print "Hello, world!"
+Hello, world!
+>>> age = 45
+>>> print "You have", 65 - age, "years until retirement"
+You have 20 years until retirement
+```
+
+---
+
+# Hands-on exercise
+
+Write a program called **age.py** which will ask the user for their birth year and will respond with the user's age in the year 2024.
+
+Example:
+
+```
+What's your name?
+> PY
+What year were you born?
+> 1982
+Hi PY! You are 42
+```
+
+---
+
+# Logic
+
+Many logical expressions use relational operators:
+
+|  Operator    |     Meaning   | Example | Result |
+| ------------ | --------------- | ----- | ------ |
+|  `==` |  equals |  `1 + 1 == 2` |  True |
+|  `!=` |  does not equal |  `3.2 != 2.5` |  True |
+|  `<` |  less than |  `10 < 5` |  False |
+|  `>` |  greater than |  `10 > 5` |  True |
+|  `<=` |  less than or equal to |  `126 <= 100` |  False |
+|  `>=` |  greater than or equal to |  `5.0 >= 5.0` |  False |
+
+---
+
+## Combining comparisons
+
+Logical expressions can be combined with logical operators:
+
+|  Operator    |      Example | Result |
+| ------------ |  ----- | ------ |
+|  `and` | `9 != 6 and 2 < 3` |  True |
+|  `or` |   `2 == 3 or -1 < 5` |  True |
+|  `not` |   `not 7 > 0` |  False |
+
+---
+
+# Selection
+
+### `if`
+
+Executes a group of statements only if a certain condition is true. Otherwise, the statements are skipped.
+
+### `if/else`
+
+Executes one block of statements if a certain condition is True, and a second block of statements if it is False.
+
+### `if/elif/else`
+
+Multiple conditions can be chained with elif ("else if")
+
+---
+
+### Examples
+
+```python
+gpa = 3.4
+if gpa > 2.0:
+  print "Your application is accepted."
+```
+
+```python
+import math
+x = 30
+if x <= 15 :
+  y = x + 15
+elif x <= 30 :
+  y = x + 30
+else :
+  y=x
+print 'y = ',
+print math.sin(y)
+```
+
+---
+
+# Hands-on exercise
+
+Write a script that asks the user to input a year and tells them wheter that year is a leap year.
+
+The rules for leap years are:
+
+- in general, a year is a leap year if it is divisible by 4 (e.g. 1904 was a leap year)
+- exception from the above: if the year is also divisible by 100 it _is not_ a leap year (e.g. 1900 was _not_ a leap year)
+- exception from the exception: if the year is also divisible by 400 it _is_ a leap year (e.g. 2000 _was_ a leap year)
+
+Hint: "x is divisible by y" in Python: `x % y == 0`
+
+---
+
+# Repetition
+
+## The `for` loop
+
+Repeats a set of statements over a group of values.
+
+```python
+for x in range(1, 6):
+  print x, "squared is", x * x
+```
+
+```
+1 squared is 1
+2 squared is 4
+3 squared is 9
+4 squared is 16
+5 squared is 25
+```
+
+---
+
+## The `range` function
+
+`range(start, stop [, step])`
+
+```python
+for x in range(5, 0, -1):
+  print x
+print "Blastoff!"
+```
+
+```
+5
+4
+3
+2
+1
+Blastoff!
+```
+
+---
+
+## Cumulative loops
+
+Some loops incrementally compute a value that is initialized outside the loop. 
+This is sometimes called a *cumulative sum*.
+
+```python
+sum = 0
+for i in range(1, 11):
+  sum = sum + (i * i)
+print "sum of first 10 squares is", sum
+```
+
+```
+sum of first 10 squares is 385
+```
+
+---
+
+## `while` loops
+
+Executes a group of statements as long as a condition is True.
+> good for indefinite loops (repeat an unknown number of times)
+
+```python
+x=1
+while x < 10 :
+  print x
+  x=x+1
+```
+
+---
+
+## Loop Control Statements
+
+- **break**: Jumps out of the closest enclosing loop
+- **continue**: Jumps to the top of the closest enclosing loop
+- **pass**: Does nothing, empty statement placeholder
+
+```python
+a = 1
+while True:
+  a = a * 2
+  print(a)
+  if (a > 1000):
+    break
+```
+
+---
+
+# Hands-on exercise
+
+Someone opens a new bank account and deposits 100€ at the start of each year. 
+At the end of a year, they get 4% interest. 
+
+How much do they have after 10 years?
+
+---
+
+# Composite types
+
+**dictionaries** are mappings that contain "named" entries with associated values.
+
+```py
+person = {
+    "first_name": "John",
+    "last_name": "Doe",
+    "nationality": "Canada",
+    "birth_year": 1980
+}
+```
+
+Retrieving and setting elements:
+
+```py
+person["first_name"]
+```
+
+```py
+person["first_name"] = "Jane"
+```
+
+---
+
+A **list** represents a sequence of objects
+
+```py
+primes = [2, 3, 5, 7, 11]
+users = ["Alice", "Bob", "Charlie"]
+
+products = [
+    {"name": "IPhone 12", "price": 949},
+    {"name": "Fairphone", "price": 419},
+    {"name": "Pixel 5", "price": 799}
+]
+```
+
+Determining the length
+
+```py
+len(users)
+```
+
+Overwriting a list element
+
+```py
+users[0] = "Andrew"
+```
+
+---
+
+Retrieving list elements via their index (starting at 0):
+
+```py
+users[0]
+users[1]
+users[-1] # last element
+```
+
+Appending an element
+
+```py
+users.append("Dora")
+```
+
+Removing the last element:
+
+```py
+users.pop()
+```
+
+Removing by index:
+
+```py
+users.pop(0)
+```
+
+---
+
+# Object references and mutations
+
+* What will be the value of `a` after this code has run?
+
+  ```py
+  a = [1, 2, 3]
+  b = a
+  b.append(4)
+  ```
+
+* An assignment (e.g. `b = a`) assigns a new (additional) **name** to an object.
+  The object in the background **is the same**.
+
+---
+
+If the original should remain intact it may be copied or a derived version can be newly created based on it:
+
+```py
+a = [1, 2, 3]
+# creating a new copy
+b = a.copy()
+# modifying b
+b.append(4)
+```
+
+```py
+a = [1, 2, 3]
+# creating a new object b based on a
+b = a + [4]
+```
+
+---
+
+- Some objects can be **mutated** (changed) directly 
+  > e.g. via `.append()`, `.pop()`, ...
+
+  Examples: `list`, `dict`
+
+- Many simple objects are **immutable** after they have been created. 
+  However, they can be replaced by other objects.
+
+  Examples: `int`, `float`, `str`, `bool`, `tuple`
+
+---
+
+**tuple** 
+
+- Area of application: similar to dicts
+
+  ```py
+  point_dict = {"x": 2, "y": 4}
+  point_tuple = (2, 4)  
+  date_dict = { "year": 1973, "month": 10, "day": 23}
+  date_tuple = (1973, 10, 23)
+  ```
+
+  Each entry in a tuple has a specific meaning
+
+- Behavior: similar to lists
+
+  ```py
+  date_tuple[0] # 1973
+  len(date_tuple) # 3
+  ```
+
+Unlike lists, tuples are immutable (no `.append` / `.pop` / ...)
+
+---
+
+# Working with files
+
+A **file** is a sequence of bytes on a storage device
+
+Many file formats are a sequence of text characters 
+> e.g. the formats _.txt_, _.html_, _.csv_ or _.py_.
+
+The content of text files can be represented as strings (ASCII).
+
+Other file contents can be represented as byte sequences (binary).
+
+---
+
+### Writing a text file
+
+```py
+file = open("message.txt", "w", encoding="utf-8")
+file.write("hello world\n")
+file.close()
+```
+
+> The file is opened for writing (w).
+> The character encoding will be UTF-8.
+
+### Reading a text file
+
+```py
+file = open("message.txt", encoding="utf-8")
+content = file.read()
+file.close()
+print(content)
+```
+
+> Standard mode: _reading_ (**r**)
+
+---
+
+### File modes
+
+```py
+# mode: text, append
+open("file.txt", mode="ta")
+```
+
+- `t`: text mode (**default**)
+- `b`: binary
+
+<!-- list-separator -->
+
+- `r`: reading (**default**)
+- `w`: (over)writing
+- `a`: appending
+
+---
+
+### Open and the with statement
+
+```py
+with open("todos.txt", encoding="utf-8") as file_obj:
+    content = file_obj.read()
+```
+
+The file will be closed automatically when the program leaves the indented block.
+
+### character encoding
+
+The default character encoding for text files depends on the operating system:
+
+```py
+import locale
+locale.getpreferredencoding()
+```
+
+> ASCII, latin1, UTF-8, etc
+
+Recommendation: Use UTF-8 (best support for special characters)
+
+---
+
+# Parts of programs
+
+- programs
+  - code blocks
+    - statements
+      - expressions
+
+### Empty code blocks
+
+_empty_ code block via the `pass` statement:
+
+```py
+# TODO: warn the user if path doesn't exist
+
+if not os.path.exists(my_path):
+    pass
+```
+
+---
+
+### Statements across multiple lines
+
+a statement can span across multiple lines if we use parantheses:
+
+```py
+a = (2 + 3 + 4 + 5 + 6 +
+     7 + 8 + 9 + 10)
+```
+
+Alternative: _escaping_ newlines with `\`
+
+```py
+a = 2 + 3 + 4 + 5 + 6 + \
+    7 + 8 + 9 + 10
+```
+
+---
+
+### Expressions
+
+_expression_ = something that produces a value (the value might be `None`)
+
+_expression_ = anything that can be on the right-hand side of an assignment (`=`)
+
+examples of expressions:
+
+- `(7 - 3) * 0.5`
+- `(7 - 3)`
+- `7`
+- `round(3.5)`
+- `x == 1`
+
+---
+
+# Function parameters
+
+## Positional parameters and keyword parameters
+
+Calling `open`:
+
+- with positional parameters:
+
+  ```py
+  f = open("myfile.txt", "w", -1, "utf-8")
+  ```
+
+- with keyword parameters:
+
+  ```py
+  f = open("myfile.txt", encoding="utf-8", mode="w")
+  ```
+
+---
+
+## Optional parameters and default parameters
+
+Some parameters of functions can be optional (they have a default value)
+
+> Example: For `open` only the first parameter is required, the others are optional
+
+The values of default parameters can be looked up in the documentation
+
+---
+
+# Defining functions
+
+```py
+def average(a, b):
+    m = (a + b) / 2
+    return m
+```
+
+### Optional parameters and default parameters
+
+This is how we define default values for parameters:
+
+```py
+def shout(phrase, end="!"):
+    print(phrase.upper() + end)
+
+shout("hello") # HELLO!
+shout("hi", ".") # HI.
+```
+
+---
+
+### Scope
+
+A function definition creates a new **scope**, an area where variables are valid
+
+In the following example there are two distinct variables named `m`:
+
+```py
+m = "Hello, world"
+
+def average(a, b):
+    m = (a + b) / 2
+    return m
+x = average(1, 2)
+
+print(m) # prints "Hello, world"
+```
+
+---
+
+### Scope
+
+Inside a function, outer variables may be read but not overwritten
+
+In other programming languages constructs like `if` or `for` usually also open a new scope - this is not the case in Python
+
+---
+
+# Modules and packages
+
+**Module** : collection of Python objects that can be imported
+
+**Package** : collection of modules
+
+> packages are actually a special type of modules
+
+- `urllib` = package
+- `urllib.request` = module
+- `urllib.request.urlopen` = function
+
+<!-- list separator -->
+
+- `sys` = module
+- `sys.path` = object
+
+---
+
+Examples:
+
+```py
+import module1
+from package2 import module2a, module2b
+from module3 import object3a, object3b
+from package4.module4 import object4a, object4b
+```
+
+```py
+import os
+from math import sqrt, pi
+```
+
+Short names:
+
+```py
+import numpy as np
+import matplotlib.pyplot as plt
+```
+
+> Importing everything from a module (usually not recommended):
+> `from math import *`
+
+---
+
+When importing _some_ packages, submodules will be imported automatically.
+
+Examples:
+
+```py
+import os
+import numpy as np
+
+os.path.join(...)
+np.random.randint(10)
+```
+
+Counterexample - this will fail:
+
+```py
+import urllib
+
+urllib.request.urlopen(...)
+```
+
+---
+
+## Conventions for imports
+
+- all imports in a Python file _should_ be at the start of the file
+- imports _should_ be split into three groups:
+  - imports from the standard library
+  - imports from other libraries
+  - imports within the project
+
+---
+
+# Local modules
+
+we can import local Python files as modules
+
+example: local file _messages.py_
+
+```py
+import messages
+
+print(messages.message1)
+```
+
+we can create so-called _packages_ as folders
+
+example: folder _phrases/_, files _phrases/messages.py_ and _phrases/greetings.py_
+
+```py
+from phrases import greetings
+
+print(greetings.greeting1)
+```
+
+---
+
+## Resolving imports
+
+Search order of imports:
+
+- directory of the Python script that was originally executed
+- standard library
+- external libraries
+
+Avoid name clashes with existing modules / packages!
+
+---
+
+# NumPy
+
+Library for efficient data processing
+
+Data are stored in multidimensional arrays of numeric values which are implemented in an efficient way:
+
+- smaller memory use than e.g. lists of numbers in Python
+- much faster execution of operations like element-wise addition of arrays
+
+Data can represent images, sound, measurements and much more
+
+Common import convention:
+
+```python
+import numpy as np
+```
+
+---
+
+# Pandas
+
+_Pandas_ is a data analysis library; it is based on _NumPy_
+
+```py
+import pandas as pd
+```
+
+### Series and DataFrame
+
+- **Series**: Collection of values for some keys (table column)
+- **DataFrame**: Collection of associated series (table)
+
+---
+
+# Plotting
+
+Basic (low-level) library for plotting: _matplotlib_
+
+Higher-level interfaces:
+
+- _pyplot_ (contained in matplotlib, similar to matlab's plotting interface)
+- _pandas_ plotting functions (based on pyplot)
+
+---
+
+### Simple plot with pyplot
+
+```py
+import numpy as np
+import matplotlib.pyplot as plt
+
+x = np.array([0, 1, 2, 3])
+
+y1 = x*2
+y2 = x**2
+
+plt.plot(x, y1)
+plt.plot(x, y2)
+```
+
+In Jupyter plots are shown automatically
+
+In a regular terminal / program:
+
+```py
+plt.show()
+```
+
+---
+
+![h:600](assets/Figure_1.png)
+
+---
+
+### Pyplot: Configuration and Styling
+
+We'll create a plot that shows the sine and cosine functions in the interval from _0_ to _2$\pi$_
+
+```py
+x = np.linspace(0, 2*np.pi, 100)
+
+sin = np.sin(x)
+cos = np.cos(x)
+```
+
+---
+
+![h:700](assets/Figure_2.png)
+
+---
+
+In the following examples we will show how to use **Cartopy** with **netCDF** ClimateData.
+
+---
+
+![h:700](assets/Figure_3.png)
+
+---
+
+![h:700](assets/Figure_4.png)
+
+---
+
+# Working with various file formats
+
+Possibilities:
+
+- text files
+- JSON
+- CSV
+- XML
+- Python object files (via pickle and shelve)
+- binary files
+
+---
+
+## JSON
+
+JSON: popular and standardized data file format
+can represent the fundamental Python datatypes (none, bool, int, float, list, dict)
+
+Saving JSON:
+
+```py
+import json
+data = ["one", "two", "three"]
+jsonstring = json.dumps(data)
+with open("numbers.json", mode="w", encoding="utf-8") as jsonfile:
+    jsonfile.write(jsonstring)
+```
+
+Reading JSON:
+
+```py
+import json
+with open("numbers.json", encoding="utf-8") as jsonfile:
+    jsonstring = jsonfile.read()
+data = json.loads(jsonstring)
+```
+
+---
+
+## CSV
+
+CSV is a file format which can hold tabular data; entries are separated by commas
+
+Example:
+
+```csv
+ISO,Country,Capital,Languages
+AD,Andorra,Andorra la Vella,"ES,FR"
+AE,United Arab Emirates,Abu Dhabi,"AE,fa,en,hi,ur"
+AF,Afghanistan,Kabul,"AF,tk"
+```
+
+Python libraries:
+
+- _csv_ (part of the standard libary)
+- _pandas_
+
+---
+
+Writing CSV via pandas:
+
+```py
+import pandas as pd
+data = pd.DataFrame(
+    [
+        ["CN", 9.6, 1386],
+        ["RU", 17.0, 144],
+        ["US", 9.8, 327],
+    ],
+    columns=["code", "area", "population"],
+)
+
+data.to_csv("countries.csv")
+```
+
+Reading CSV via pandas:
+
+```py
+import pandas as pd
+data = pd.read_csv("countries.csv")
+print(data)
+print(data.values.tolist())
+```
+
+---
+
+Reading and writing CSV
+
+```py
+import csv
+
+data = [
+    ['code', 'area', 'population'],
+    ['CN', 9.6, 1386],
+    ['RU', 17, 144],
+    ['US', 9.8, 327]
+]
+
+with open('countr.csv', 'w', encoding='utf-8', newline='') as f:
+    writer = csv.writer(f)
+    writer.writerows(data)
+
+with open('countr.csv', encoding='utf-8', newline='') as f:
+    reader = csv.reader(f)
+    for row in reader:
+        print(row)
+```
+
+---
+
+Thank you for your attention<!--fit-->
+===
\ No newline at end of file
diff --git a/learning-python/Python.pdf b/learning-python/Python.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..3456a506e9e9d4dbb46f8bbce8b4df8819d04923
Binary files /dev/null and b/learning-python/Python.pdf differ
diff --git a/learning-python/README.md b/learning-python/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..af22d62d563341f353892321de6df9af297a585c
--- /dev/null
+++ b/learning-python/README.md
@@ -0,0 +1,7 @@
+# learning-python
+
+This is the repository for the training Python
+
+You can find the slides [here](./Python.pdf).
+
+Presentation is [here](https://www.elic.ucl.ac.be/users/pbarriat/slides/Python.html)
diff --git a/learning-python/assets/Figure_1.png b/learning-python/assets/Figure_1.png
new file mode 100644
index 0000000000000000000000000000000000000000..2a5e598f6145805476e72027709613ca6b607764
Binary files /dev/null and b/learning-python/assets/Figure_1.png differ
diff --git a/learning-python/assets/Figure_2.png b/learning-python/assets/Figure_2.png
new file mode 100644
index 0000000000000000000000000000000000000000..0cf48fe5319b875d0cc929c4c9d5e8aabb4bfebf
Binary files /dev/null and b/learning-python/assets/Figure_2.png differ
diff --git a/learning-python/assets/Figure_2.py b/learning-python/assets/Figure_2.py
new file mode 100644
index 0000000000000000000000000000000000000000..75dc24cfc0fda5636e980ecd22e9b9c9d42d15f5
--- /dev/null
+++ b/learning-python/assets/Figure_2.py
@@ -0,0 +1,24 @@
+import matplotlib.pyplot as plt
+import numpy as np
+
+plt.style.use("seaborn")
+
+x = np.linspace(0, 2*np.pi, 100)
+sin = np.sin(x)
+cos = np.cos(x)
+plt.plot(x, sin, "C0--", label="sin(x)")
+plt.plot(x, cos, "C1:", label="cos(x)")
+
+pi_multiples = np.array([0, 0.5, 1, 1.5, 2]) * np.pi
+sin_points = np.sin(pi_multiples)
+cos_points = np.cos(pi_multiples)
+plt.plot(pi_multiples, sin_points, "C0o")
+plt.plot(pi_multiples, cos_points, "C1o")
+
+plt.title("Trigonometric functions")
+plt.xlabel("x (radians)")
+plt.xticks(np.linspace(0, 2*np.pi, 5))
+plt.legend()
+plt.axis("scaled")
+
+plt.show()
diff --git a/learning-python/assets/Figure_3.png b/learning-python/assets/Figure_3.png
new file mode 100644
index 0000000000000000000000000000000000000000..b4dd6cdb917ca0479fb463791994d78602e6a6ef
Binary files /dev/null and b/learning-python/assets/Figure_3.png differ
diff --git a/learning-python/assets/Figure_3.py b/learning-python/assets/Figure_3.py
new file mode 100644
index 0000000000000000000000000000000000000000..fcfa6cb3b339b9c7444f7e6bd857603b3cf01528
--- /dev/null
+++ b/learning-python/assets/Figure_3.py
@@ -0,0 +1,60 @@
+#
+# Input data
+#
+# We are going to use a netCDF file for testing the following code. 
+# The test file islocated in a dedicated online repository:
+#    https://nextcloud.cism.ucl.ac.be/s/H7XFSi8J4E8dnRK
+#
+# Download the netCDF prmsl.2000.nc : 
+#    a classic (regular grid) pressure file 
+#
+
+from netCDF4 import Dataset
+import numpy as np
+import cartopy.crs as ccrs
+import cartopy.feature as cfeature
+from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
+from cartopy.util import add_cyclic_point
+import matplotlib.pyplot as plt
+import matplotlib.ticker as mticker
+
+my_example_nc_file = 'prmsl.2000.nc'
+fh = Dataset(my_example_nc_file, mode='r')
+
+lons = fh.variables['lon'][:]
+lats = fh.variables['lat'][:]
+times = fh.variables['time'][:]
+times_unit = fh.variables['time'].units
+var = fh.variables['prmsl']
+var_units = fh.variables['prmsl'].units
+
+#fig = plt.subplots(figsize=(8, 6), dpi=102)
+plt.axis('off')
+
+ax = plt.axes(projection=ccrs.Robinson(central_longitude=10.0))
+ax.coastlines()
+
+# Plot Data
+data = np.squeeze(var[1,:,:])
+#cs = ax.pcolormesh(lons, lats, data, transform=ccrs.PlateCarree(), shading='auto')
+lon = np.arange(0, 360, 2)
+data, lon = add_cyclic_point(data, coord=lon)
+cs = ax.contourf(lon, lats, data, transform=ccrs.PlateCarree(), transform_first=False)
+
+# Add Grid Lines
+gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=0.5, color="grey")
+gl.xlocator = mticker.FixedLocator(np.arange(-170,191,36))
+gl.ylocator = mticker.FixedLocator(np.arange(-90,90,15))
+gl.rotate_labels = False
+gl.top_labels = False
+gl.xformatter = LONGITUDE_FORMATTER
+gl.yformatter = LATITUDE_FORMATTER
+
+# Add Colorbar
+cbar = plt.colorbar(cs,orientation="horizontal")
+cbar.set_label(var_units)
+
+# Add Title
+plt.title('Daily Pressure at Mean Sea Level')
+
+plt.show()
diff --git a/learning-python/assets/Figure_4.png b/learning-python/assets/Figure_4.png
new file mode 100644
index 0000000000000000000000000000000000000000..0eea81ed7e00cb25675056117f9622d3e32d1b0c
Binary files /dev/null and b/learning-python/assets/Figure_4.png differ
diff --git a/learning-python/assets/Figure_4.py b/learning-python/assets/Figure_4.py
new file mode 100644
index 0000000000000000000000000000000000000000..a4558a03fa69bd6f9e757f5a0e1a7ab23cfb1f07
--- /dev/null
+++ b/learning-python/assets/Figure_4.py
@@ -0,0 +1,75 @@
+#
+# Input data
+#
+# We are going to use a netCDF file for testing the following code. 
+# The test file islocated in a dedicated online repository:
+#    https://nextcloud.cism.ucl.ac.be/s/H7XFSi8J4E8dnRK
+#
+# Download the netCDF prmsl.2000.nc : 
+#    a classic (regular grid) pressure file 
+#
+
+from netCDF4 import Dataset
+import numpy as np
+import cartopy.crs as ccrs
+import cartopy.feature as cfeature
+from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
+from cartopy.util import add_cyclic_point
+import matplotlib.pyplot as plt
+import matplotlib.ticker as mticker
+import matplotlib.path as mpath
+
+my_example_nc_file = 'prmsl.2000.nc'
+fh = Dataset(my_example_nc_file, mode='r')
+
+lons = fh.variables['lon'][:]
+lats = fh.variables['lat'][:]
+times = fh.variables['time'][:]
+times_unit = fh.variables['time'].units
+var = fh.variables['prmsl']
+var_units = fh.variables['prmsl'].units
+
+#------------------------------------------------------------
+# Compute a circle in axes coordinates, 
+# which we can use as a boundary for the map.
+# https://scitools.org.uk/cartopy/docs/latest/gallery/lines_and_polygons/always_circular_stereo.html
+
+theta = np.linspace(0, 2*np.pi, 100)
+center, radius = [0.5, 0.5], 0.5
+verts = np.vstack([np.sin(theta), np.cos(theta)]).T
+circle = mpath.Path(verts * radius + center)
+
+fig = plt.subplots(figsize=(6, 6), dpi=102)
+plt.axis('off')
+
+ax = plt.axes(projection=ccrs.Orthographic(central_longitude=0.0, central_latitude=90.0))
+xlims = [-180,180]
+ylims = [50,90]
+ax.set_extent(xlims+ylims, crs=ccrs.PlateCarree())
+ax.set_boundary(circle, transform=ax.transAxes)
+
+ax.stock_img()
+ax.coastlines("110m", linewidth=0.5, color="black")
+
+# Plot Data
+data = np.squeeze(var[1,:,:])
+#cs = ax.pcolormesh(lons, lats, data, transform=ccrs.PlateCarree(), shading='auto')
+lon = np.arange(0, 360, 2)
+data, lon = add_cyclic_point(data, coord=lon)
+cs = ax.contourf(lon, lats, data, transform=ccrs.PlateCarree(), transform_first=False)
+
+# Doing the gridlines we want 
+gl = ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=0.5, color="grey")
+gl.xformatter = LONGITUDE_FORMATTER
+gl.yformatter = LATITUDE_FORMATTER
+gl.xlocator = mticker.FixedLocator(np.arange(-180,181,18))
+gl.ylocator = mticker.FixedLocator(np.arange(-90,91,10))
+
+# Add Colorbar
+cbar = plt.colorbar(cs,orientation="horizontal")
+cbar.set_label(var_units)
+
+# Add Title
+plt.title('Daily Pressure at Mean Sea Level')
+
+plt.show()
diff --git a/learning-python/assets/back.png b/learning-python/assets/back.png
new file mode 100644
index 0000000000000000000000000000000000000000..0674657248db1242420acd99ec7d510b8f3236cd
Binary files /dev/null and b/learning-python/assets/back.png differ
diff --git a/learning-python/assets/garde.png b/learning-python/assets/garde.png
new file mode 100644
index 0000000000000000000000000000000000000000..7c82359c3ac6126ffeed79a081ab2c75c00e19dc
Binary files /dev/null and b/learning-python/assets/garde.png differ
diff --git a/learning-python/assets/kroki-plugin.js b/learning-python/assets/kroki-plugin.js
new file mode 100644
index 0000000000000000000000000000000000000000..f0baa0a5eb82d2e4f80f1398721ac8a69836a85a
--- /dev/null
+++ b/learning-python/assets/kroki-plugin.js
@@ -0,0 +1,52 @@
+const { deflateSync } = require('zlib')
+
+const krokiLangs = [
+  'actdiag',
+  'blockdiag',
+  'bpmn',
+  'bytefield',
+  'c4plantuml',
+  'ditaa',
+  'dot',
+  'erd',
+  'excalidraw',
+  'graphviz',
+  'mermaid',
+  'nomnoml',
+  'nwdiag',
+  'packetdiag',
+  'pikchr',
+  'plantuml',
+  'rackdiag',
+  'seqdiag',
+  'svgbob',
+  'umlet',
+  'vega',
+  'vegalite',
+  'wavedrom',
+]
+
+const entrypoint = 'https://kroki.io/'
+
+const marpKrokiPlugin = (md) => {
+  const { fence } = md.renderer.rules
+
+  md.renderer.rules.fence = (tokens, idx, options, env, self) => {
+    const info = md.utils.unescapeAll(tokens[idx].info).trim()
+
+    if (info) {
+      const [lang] = info.split(/(\s+)/g)
+
+      if (krokiLangs.includes(lang)) {
+        const data = deflateSync(tokens[idx].content).toString('base64url')
+
+        // <marp-auto-scaling> is working only with Marp Core v3
+        return `<p><marp-auto-scaling data-downscale-only><img src="${entrypoint}${lang}/svg/${data}"/></marp-auto-scaling></p>`
+      }
+    }
+
+    return fence.call(self, tokens, idx, options, env, self)
+  }
+}
+
+module.exports = marpKrokiPlugin
diff --git a/learning-python/assets/marp.config.js b/learning-python/assets/marp.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..c5a35bca5b0623dc640ce779365a6d00bf5dab64
--- /dev/null
+++ b/learning-python/assets/marp.config.js
@@ -0,0 +1,5 @@
+const marpKrokiPlugin = require('./kroki-plugin')
+
+module.exports = {
+  engine: ({ marp }) => marp.use(marpKrokiPlugin)
+}
diff --git a/learning-python/assets/prmsl.2000.nc b/learning-python/assets/prmsl.2000.nc
new file mode 100644
index 0000000000000000000000000000000000000000..eb3f88879fc1167dd80a2700c00221c74a61ddbc
Binary files /dev/null and b/learning-python/assets/prmsl.2000.nc differ
diff --git a/learning-python/assets/python.svg b/learning-python/assets/python.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e771ee25415b2151239834d1abca3926a8f9aea5
--- /dev/null
+++ b/learning-python/assets/python.svg
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?><!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
+<svg width="800px" height="800px" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M31.885 16c-8.124 0-7.617 3.523-7.617 3.523l.01 3.65h7.752v1.095H21.197S16 23.678 16 31.876c0 8.196 4.537 7.906 4.537 7.906h2.708v-3.804s-.146-4.537 4.465-4.537h7.688s4.32.07 4.32-4.175v-7.019S40.374 16 31.885 16zm-4.275 2.454c.771 0 1.395.624 1.395 1.395s-.624 1.395-1.395 1.395a1.393 1.393 0 0 1-1.395-1.395c0-.771.624-1.395 1.395-1.395z" fill="url(#a)"/><path d="M32.115 47.833c8.124 0 7.617-3.523 7.617-3.523l-.01-3.65H31.97v-1.095h10.832S48 40.155 48 31.958c0-8.197-4.537-7.906-4.537-7.906h-2.708v3.803s.146 4.537-4.465 4.537h-7.688s-4.32-.07-4.32 4.175v7.019s-.656 4.247 7.833 4.247zm4.275-2.454a1.393 1.393 0 0 1-1.395-1.395c0-.77.624-1.394 1.395-1.394s1.395.623 1.395 1.394c0 .772-.624 1.395-1.395 1.395z" fill="url(#b)"/><defs><linearGradient id="a" x1="19.075" y1="18.782" x2="34.898" y2="34.658" gradientUnits="userSpaceOnUse"><stop stop-color="#387EB8"/><stop offset="1" stop-color="#366994"/></linearGradient><linearGradient id="b" x1="28.809" y1="28.882" x2="45.803" y2="45.163" gradientUnits="userSpaceOnUse"><stop stop-color="#FFE052"/><stop offset="1" stop-color="#FFC331"/></linearGradient></defs></svg>
\ No newline at end of file
diff --git a/learning-python/assets/python_logo.png b/learning-python/assets/python_logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..1cbf71a855276f690fc1aff722993843f79b7bb2
Binary files /dev/null and b/learning-python/assets/python_logo.png differ
diff --git a/learning-python/assets/tum.css b/learning-python/assets/tum.css
new file mode 100644
index 0000000000000000000000000000000000000000..08d289362d7399b163db43fbc0e37f9b4a6c9397
--- /dev/null
+++ b/learning-python/assets/tum.css
@@ -0,0 +1,55 @@
+/* @theme tum */
+
+@import 'default';
+
+section {
+  /*background-color: #fff;
+  color: #000;
+  background-image: url('images/TUM_Logo_blau_rgb_s.svg');
+  background-repeat: no-repeat;
+  background-position: right 40px top 40px;
+  background-size: 8%;*/
+}
+
+section.lead {
+  /*background-image: url('images/TUM_Uhrenturm.png');
+  background-position: right;
+  background-size: 45%;*/
+}
+
+section h1,
+section h2 {
+  color: #1f315c;
+}
+section a {
+  color: #5fb2e6;
+}
+section footer,
+section::after {
+  color: #9cb7d4;
+}
+
+section.invert {
+  background-color: #003359;
+  color: #fff;
+  /*background-image: url('images/TUM_Logo_weiss_rgb_s.svg');*/
+}
+
+section.lead.invert {
+  /*background-image: url('images/TUM_Uhrenturm_w.png');*/
+}
+
+section.invert h1,
+section.invert footer,
+section.invert::after {
+  color: #fff;
+}
+
+section.invert a {
+  color: #e37222;
+}
+
+/* Add "Page" prefix and total page number */
+section::after {
+  content: attr(data-marpit-pagination) ' / ' attr(data-marpit-pagination-total);
+}
diff --git a/learning-python/compile.sh b/learning-python/compile.sh
new file mode 100755
index 0000000000000000000000000000000000000000..605c32557ecc6b6896717aa3afe9a92ebe8e0e92
--- /dev/null
+++ b/learning-python/compile.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+#
+# PY Barriat, September 2024
+#
+# Download and install marp (MarkDown slides extension) from here:
+# https://github.com/marp-team/marp-cli/releases
+#
+# Install npm (needed for mermaid: nice extension to make diagramm)
+# npm i
+# sudo apt install npm
+#
+
+rm -f ./Python.pdf ./Python.html
+
+npx marp --allow-local-files --theme ./assets/tum.css -c ./assets/marp.config.js ./Python.md -o Python.pdf
+npx marp --template bespoke --bespoke.progress --allow-local-files --theme ./assets/tum.css -c ./assets/marp.config.js Python.md -o Python.html