diff --git a/python/python/2_Functions_Classes_Modules.ipynb b/python/python/2_Functions_Classes_Modules.ipynb index 00d8238..a0a55f2 100644 --- a/python/python/2_Functions_Classes_Modules.ipynb +++ b/python/python/2_Functions_Classes_Modules.ipynb @@ -21,7 +21,7 @@ "source": [ "# Functions\n", "\n", - "In programming, a [function](https://docs.python.org/3.7/tutorial/controlflow.html#defining-functions) is a named section of a program that performs a specific task.\n", + "In programming, a [function](https://docs.python.org/3/tutorial/controlflow.html#defining-functions) is a named section of a program that performs a specific task.\n", "\n", "## How to create a function?\n", "\n", @@ -78,8 +78,6 @@ "outputs": [], "source": [ "def myadd(myparam1, myparam2):\n", - " print(f\"My first parameter is {myparam1}.\")\n", - " print(f\"My second parameter is {myparam2}.\")\n", " return myparam1 + myparam2" ] }, @@ -163,7 +161,7 @@ "\n", "* The function's signature defines a set of parameters to be given when calling a function.\n", "* Default values for a parameter can be given after an `=` sign.\n", - "* There are more options for function parameters, like defining arbitrary argument list, that won't be discussed today but are presented in the [official Python documentation](https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions) or in [PythonCentral tutorial](https://www.pythoncentral.io/fun-with-python-function-parameters)." + "* There are more options for function parameters, like defining arbitrary argument list, that won't be discussed today but are presented in the [official Python documentation](https://docs.python.org/3/tutorial/controlflow.html#more-on-defining-functions)." ] }, { @@ -287,12 +285,9 @@ }, "outputs": [], "source": [ - "# Solution\n", - "import inspect\n", - "from solution_quadratic_function import polynom\n", - "\n", - "print(\"Solution:\")\n", - "print(inspect.getsource(polynom))" + "# Solution - please refer to \"solution_quadratic_function.py\"\n", + "with open(\"solution_quadratic_function.py\", \"r\") as f:\n", + " print(f.read())" ] }, { @@ -306,104 +301,7 @@ "outputs": [], "source": [ "# Example of usage.\n", - "polynom(1, -1, -2)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "## Warning about default mutable objects\n", - "\n", - "![warning](img/warning.png)\n", - "\n", - "Never use mutable objects, typically `list` or a `dictionary`, as default parameter, or you will have unexpected consequences!\n", - "\n", - "Example:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "-" - } - }, - "outputs": [], - "source": [ - "def bad_append(a, any_list=[]):\n", - " any_list.append(a)\n", - " return any_list" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "print(bad_append(1))\n", - "print(bad_append(2))\n", - "print(bad_append(3))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "### Reason\n", - "\n", - "The default value is evaluated only **once** when the function is defined **and not at each call**.\n", - "\n", - "### Solution\n", - "\n", - "The default value should be `None`, which is immutable, and the function should initialize an empty container, if needed.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "outputs": [], - "source": [ - "def good_append(a, any_list=None):\n", - " if any_list is None:\n", - " any_list = []\n", - " any_list.append(a)\n", - " return any_list\n", - "\n", - "\n", - "print(good_append(1))\n", - "print(good_append(2))\n", - "print(good_append(3))" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "slide" - } - }, - "source": [ - "# Possible \"advanced\" syntaxes" + "# polynom(1, -1, -2)" ] }, { @@ -414,7 +312,7 @@ } }, "source": [ - "## The `lambda` function\n", + "## `lambda` functions\n", "\n", "One can define an **anonymous** function, sometimes called a lambda function.\n", "\n", @@ -469,93 +367,6 @@ "pow2(5)" ] }, - { - "cell_type": "markdown", - "metadata": { - "slideshow": { - "slide_type": "subslide" - } - }, - "source": [ - "## Python typing\n", - "\n", - "Since Python 3.5 developers can provide [type hints](https://docs.python.org/3/library/typing.html) (float, int, tuple...). This is **optional**, unused at runtime.\n", - "But they can be used by third party tools (type checkers, IDEs, linters ...)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "def my_sum_of_int(a: int, b: int) -> int:\n", - " \"\"\"\n", - " some of two ints\n", - " \"\"\"\n", - " return a + b" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "my_sum_of_int(12, 15)" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "my_sum_of_int(\"toto\", \"tata\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "def incremental_fct(a: int, incremental_value: int=1) -> int:\n", - " \"\"\"\n", - " increment 'a' by 'incremental_value'\n", - " \"\"\"\n", - " return a + incremental_value" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "slideshow": { - "slide_type": "fragment" - } - }, - "outputs": [], - "source": [ - "incremental_fct(1)" - ] - }, { "cell_type": "markdown", "metadata": { @@ -1220,6 +1031,201 @@ "source": [ "safe_div(5, 3)\n" ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "----" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "# More advanced topics" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "## Warning about default mutable objects\n", + "\n", + "![warning](img/warning.png)\n", + "\n", + "Never use mutable objects, typically `list` or a `dictionary`, as default parameter, or you will have unexpected consequences!\n", + "\n", + "Example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "-" + } + }, + "outputs": [], + "source": [ + "def bad_append(a, any_list=[]):\n", + " any_list.append(a)\n", + " return any_list" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "print(bad_append(1))\n", + "print(bad_append(2))\n", + "print(bad_append(3))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "### Reason\n", + "\n", + "The default value is evaluated only **once** when the function is defined **and not at each call**.\n", + "\n", + "### Solution\n", + "\n", + "The default value should be `None`, which is immutable, and the function should initialize an empty container, if needed.\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "outputs": [], + "source": [ + "def good_append(a, any_list=None):\n", + " if any_list is None:\n", + " any_list = []\n", + " any_list.append(a)\n", + " return any_list\n", + "\n", + "\n", + "print(good_append(1))\n", + "print(good_append(2))\n", + "print(good_append(3))" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "subslide" + } + }, + "source": [ + "## Python typing\n", + "\n", + "Since Python 3.5 developers can provide [type hints](https://docs.python.org/3/library/typing.html) (float, int, tuple...). This is **optional**, unused at runtime.\n", + "But they can be used by third party tools (type checkers, IDEs, linters ...)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def my_sum_of_int(a: int, b: int) -> int:\n", + " \"\"\"\n", + " some of two ints\n", + " \"\"\"\n", + " return a + b" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "my_sum_of_int(12, 15)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "my_sum_of_int(\"toto\", \"tata\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "def incremental_fct(a: int, incremental_value: int=1) -> int:\n", + " \"\"\"\n", + " increment 'a' by 'incremental_value'\n", + " \"\"\"\n", + " return a + incremental_value" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "slideshow": { + "slide_type": "fragment" + } + }, + "outputs": [], + "source": [ + "incremental_fct(1)" + ] } ], "metadata": { @@ -1239,7 +1245,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.13" + "version": "3.11.2" }, "widgets": { "application/vnd.jupyter.widget-state+json": { diff --git a/python/python/3_input_output.ipynb b/python/python/3_input_output.ipynb index a404683..4b0ff86 100644 --- a/python/python/3_input_output.ipynb +++ b/python/python/3_input_output.ipynb @@ -8,14 +8,19 @@ } }, "source": [ - "# Input and Output" + "# Input and Output\n", + "\n", + "* In this section, we see how to do **simple** file handling with `open()`. \n", + "* More often than not, you **won't** need to do that\n", + " * For text files, use `numpy.loadtxt()` or `numpy.genfromtxt`\n", + " * Otherwise, use dedicated libraries (h5py, fabio, ...)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { - "slide_type": "fragment" + "slide_type": "slide" } }, "source": [ @@ -67,9 +72,9 @@ "source": [ "import time\n", "\n", - "fn1 = \"example_io1\"\n", + "filename = \"example_file.txt\"\n", "\n", - "f = open(fn1, \"w\")\n", + "f = open(filename, \"w\")\n", "f.write(\"It is now: \" + time.ctime())\n", "f.flush() # Optional\n", "f.close() # Mandatory" @@ -101,15 +106,15 @@ }, "outputs": [], "source": [ - "print(time.ctime())\n", - "print(open(fn1).read())" + "print(\"Current time:\", time.ctime())\n", + "print(\"Time recorded in file:\", open(fn1).read())" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "fragment" } }, "source": [ @@ -139,7 +144,7 @@ "execution_count": null, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "fragment" } }, "outputs": [], @@ -155,7 +160,7 @@ "cell_type": "markdown", "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "fragment" } }, "source": [ @@ -167,7 +172,7 @@ "execution_count": null, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "fragment" } }, "outputs": [], @@ -180,7 +185,7 @@ "cell_type": "markdown", "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "slide" } }, "source": [ @@ -213,7 +218,7 @@ "execution_count": null, "metadata": { "slideshow": { - "slide_type": "-" + "slide_type": "fragment" } }, "outputs": [], @@ -332,27 +337,52 @@ "source": [ "## Hands on\n", "\n", - "Read an ascii spreadsheet written by FIT2D:\n", + "A scattering geometry can be entirely described with a Point of Normal Incidence (PONI). \n", + "This is the coordinates of the orthogonal projection of center of sample onto the detector.\n", "\n", - "* The first non commented line looks like:\n", - " * `512 512 Start pixel = ( 1 1 )`\n", - " * Then 512 values per line, 512 lines\n", - "* Read the file as a list of lists and display as an image.\n", "\n", - "If `data` is a list of lists (of float), this can be done using matlab with:\n", - "\n", - "``` python\n", - "%matplotlib inline\n", - "from matplotlib.pyplot import subplots\n", + "![sd](img/PONI.png)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "slideshow": { + "slide_type": "slide" + } + }, + "source": [ + "[pyFAI](https://pyfai.readthedocs.io/en/latest/) uses a PONI file to describe the geometry. \n", + "It looks like this:\n", "\n", - "fig, ax = subplots()\n", - "ax.imshow(data)\n", + "```yaml\n", + "poni_version: 2.1\n", + "Detector: Eiger2_4M\n", + "Detector_config: {\"orientation\": 3}\n", + "Distance: 0.41826947072547654\n", + "Poni1: 0.1273645012076586\n", + "Poni2: 0.04721622971911041\n", + "Rot1: -0.006558391003187438\n", + "Rot2: -0.001319758222137916\n", + "Rot3: 4.987612019731912e-06\n", + "Wavelength: 1.6531226457760035e-11\n", "```\n", "\n", - "* Example file in : data/example.spr\n", - "\n", - "\n", - "![FIT2D image saved in ASCII](img/fit2d_ascii_file.png \"FIT2D image saved in ASCII\")\n" + "**Exercise** read the above information from the file `data/eiger.poni`, and store it in a dictionary. \n", + "It should look like: \n", + "```python\n", + "{'poni_version': '2.1',\n", + " 'Detector': 'Eiger2_4M',\n", + " 'Detector_config': ' {\"orientation\" 3}',\n", + " 'Distance': '0.41826947072547654',\n", + " 'Poni1': '0.1273645012076586',\n", + " 'Poni2': '0.04721622971911041',\n", + " 'Rot1': '-0.006558391003187438',\n", + " 'Rot2': '-0.001319758222137916',\n", + " 'Rot3': '4.987612019731912e-06',\n", + " 'Wavelength': '1.6531226457760035e-11'\n", + "}\n", + "```" ] }, { @@ -376,38 +406,31 @@ }, "outputs": [], "source": [ - "def readspr(filepath):\n", - " \"Read a FIT2D ascii spread file\"\n", - " result = []\n", - " xsize = 0\n", - " ysize = 0\n", + "def read_poni(filepath, comment=\"#\"):\n", + " \"Read a PONI file\"\n", + " poni_info = {}\n", " with open(filepath, \"r\") as opened_file:\n", - " for idx, line in enumerate(opened_file):\n", - " strippedline = line.strip()\n", - " # if this is a commented line\n", - " if strippedline.startswith(\"#\"):\n", + " for line in opened_file:\n", + " # Sanitize current line: remove leading and trailing spaces\n", + " line = line.strip()\n", + " # Skip commented lines\n", + " if line.startswith(comment):\n", + " continue\n", + " # Split the line before and after the \":\" separator\n", + " fields = line.split(\":\")\n", + " name = fields[0]\n", + " if len(fields) == 2:\n", + " value = fields[1].strip()\n", + " elif len(fields) > 2:\n", + " value = \"\".join(fields[1:])\n", + " else:\n", " continue\n", - " words = strippedline.split()\n", - " if (len(words) == 8) and (words[2:6] == [\"Start\", \"pixel\", \"=\", \"(\"]):\n", - " xsize = int(words[0])\n", - " ysize = int(words[1])\n", - " print(\"Dimensions of the size are (%s, %s)\" % (xsize, ysize))\n", - " break\n", - " if xsize and ysize:\n", - " for line in opened_file:\n", - " words = line.split()\n", - " if len(words) != xsize:\n", - " print(\n", - " \"Error !!! Expected entries are %s, got %s\"\n", - " % (xsize, len(words))\n", - " )\n", - " return None\n", - " else:\n", - " result.append([float(i) for i in words])\n", - " return result\n", - "\n", - "\n", - "data = readspr(\"data/example.spr\")" + " value = value.strip()\n", + " poni_info[name] = value\n", + " return poni_info\n", + "\n", + "\n", + "data = read_poni(\"data/eiger.poni\")" ] }, { @@ -420,12 +443,7 @@ }, "outputs": [], "source": [ - "# Display the image\n", - "%matplotlib inline\n", - "from matplotlib.pyplot import subplots\n", - "\n", - "fig, ax = subplots()\n", - "ax.imshow(data)" + "data" ] }, { diff --git a/python/python/data/eiger.poni b/python/python/data/eiger.poni new file mode 100644 index 0000000..6aa8b9b --- /dev/null +++ b/python/python/data/eiger.poni @@ -0,0 +1,12 @@ +# Nota: C-Order, 1 refers to the Y axis, 2 to the X axis +# Calibration done at Mon Feb 5 15:50:13 2024 +poni_version: 2.1 +Detector: Eiger2_4M +Detector_config: {"orientation": 3} +Distance: 0.41826947072547654 +Poni1: 0.1273645012076586 +Poni2: 0.04721622971911041 +Rot1: -0.006558391003187438 +Rot2: -0.001319758222137916 +Rot3: 4.987612019731912e-06 +Wavelength: 1.6531226457760035e-11 diff --git a/python/python/img/PONI.png b/python/python/img/PONI.png new file mode 100644 index 0000000..a8ab98e Binary files /dev/null and b/python/python/img/PONI.png differ