Skip to content

Commit

Permalink
docs update
Browse files Browse the repository at this point in the history
  • Loading branch information
Jashcraf committed Jul 11, 2024
1 parent 967099d commit 334a67e
Show file tree
Hide file tree
Showing 5 changed files with 110 additions and 91 deletions.
6 changes: 3 additions & 3 deletions docs/notebooks/BroadcastedPolarimetry.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@
"source": [
"# Fast, Broadcasted Mueller Polarimetry\n",
"\n",
"_written by Jaren N. Ashcraft_\n",
"\n",
"`katsu` was built in part to understand the spatially-varying polarized response of optics. Manufacturing errors can leave phase and amplitude aberrations in an optical beam, and understanding these sources of error is critical to understanding our system.\n",
"\n",
"To spatially resolve this data, we need to be able to do full polarimetry on an array of pixels - so a naive implementation of the algorithm will take a very long time to run. Here, we show off the numpy optimizations done to speed up the polarimetric data reduction for Full Mueller Polarimetry.\n",
"\n",
"Readers should first be familiar with the Full Mueller Polarimetry demo\n",
"\n",
"_written by Jaren Ashcraft_"
"Readers should first be familiar with the Full Mueller Polarimetry demo"
]
},
{
Expand Down
98 changes: 55 additions & 43 deletions docs/notebooks/CalibratingPolarimeters.ipynb
Original file line number Diff line number Diff line change
@@ -1,8 +1,21 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Calibrating Mueller Polarimeters - Simulation\n",
"\n",
"In this tutorial we will review how `katsu` can be used to perform the calibration of DRRPs. Generally this technique is somewhat model-based, because it requires the construction of a polarization data reduction matrix $\\mathbf{W}$ from models of our polarization state generator and analyzer.\n",
"\n",
"But, what if you don't know their exact state? \n",
"\n",
"It's common to use commercial off-the-shelf components for our waveplates and polarizers, but the retardence and diattenuation are not guarenteed for these"
]
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": 12,
"metadata": {},
"outputs": [],
"source": [
Expand All @@ -12,105 +25,104 @@
" linear_polarizer,\n",
" linear_retarder,\n",
" linear_diattenuator\n",
")"
")\n",
"\n",
"from katsu.polarimetry import broadcasted_full_mueller_polarimetry"
]
},
{
"cell_type": "code",
"execution_count": 3,
"execution_count": 7,
"metadata": {},
"outputs": [],
"source": [
"polarizer_angle_1 = np.random.random()\n",
"polarizer_angle_2 = np.random.random()\n",
"\n",
"retarder_angle_1 = np.random.random()\n",
"retarder_angle_2 = np.random.random()"
"guess_pol_params = [0, # psgpol angle\n",
" np.pi/2, # psa angle\n",
" 0, # psgret angle\n",
" 0, # psaret angle\n",
" np.pi/2, # psgret retardance\n",
" np.pi/2] # psaret retardance\n",
"true_pol_params = [p + np.random.random()/10 for p in guess_pol_params]"
]
},
{
"cell_type": "code",
"execution_count": 16,
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [],
"source": [
"def m_fwd(a):\n",
"\n",
" polang1, polang2, retang1, retang2 = a[0], a[1], a[2], a[3]\n",
"\n",
" ## Build the forward model\n",
" # build PSG\n",
" psg = linear_retarder(retang1, np.pi/2) @ linear_polarizer(polang1)\n",
" polang1, polang2, retang1, retang2, ret1, ret2 = a[0], a[1], a[2], a[3], a[4], a[5]\n",
"\n",
" # build PSA\n",
" psa = linear_polarizer(polang2) @ linear_retarder(retang2, np.pi/2)\n",
"\n",
" # system\n",
" total = psa @ psg\n",
"\n",
" # modeled irradiance\n",
" I = total[..., 0, 0]\n",
"\n",
" # Now the truth\n",
" true_M = linear_polarizer(np.pi/2) @ linear_retarder(np.pi/4, np.pi/2) @ linear_retarder(np.pi/10, np.pi/2) @ linear_polarizer(0)\n",
" true_I = true_M[..., 0, 0]\n",
" \n",
"\n",
" return (I - true_I)**2"
]
},
{
"cell_type": "code",
"execution_count": 20,
"execution_count": 9,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" fun: 3.3420258623272954e-15\n",
" hess_inv: <4x4 LbfgsInvHessProduct with dtype=float64>\n",
" jac: array([-4.29546817e-08, 4.24794664e-08, 6.16342601e-08, -5.34759102e-08])\n",
" message: 'CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL'\n",
" nfev: 40\n",
" nit: 6\n",
" njev: 8\n",
" status: 0\n",
" message: CONVERGENCE: NORM_OF_PROJECTED_GRADIENT_<=_PGTOL\n",
" success: True\n",
" x: array([-0.1455775 , 0.09747455, -0.13116726, 1.2432434 ])\n"
" status: 0\n",
" fun: 4.7678044542371367e-14\n",
" x: [ 5.408e-01 1.035e-01 5.786e-01 6.867e-01 1.050e-02\n",
" 6.440e-01]\n",
" nit: 4\n",
" jac: [-1.269e-07 1.741e-07 -1.270e-09 -4.341e-08 -4.594e-09\n",
" -1.737e-08]\n",
" nfev: 42\n",
" njev: 6\n",
" hess_inv: <6x6 LbfgsInvHessProduct with dtype=float64>\n"
]
}
],
"source": [
"from scipy.optimize import minimize\n",
"results = minimize(m_fwd,x0=(polarizer_angle_1,polarizer_angle_2,retarder_angle_1,retarder_angle_2),method='L-BFGS-B')\n",
"results = minimize(m_fwd,x0=guess_pol_params,method='L-BFGS-B')\n",
"print(results)"
]
},
{
"cell_type": "code",
"execution_count": 22,
"execution_count": 11,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[0, 0.3141592653589793, 0.7853981633974483, 1.5707963267948966]\n"
"[0.525556233612956, 0.1242341545076937, 0.5783983696215821, 0.6813338883135133, 0.00985510712712001, 0.6418720464960674]\n"
]
},
{
"data": {
"text/plain": [
"array([-0.1455775 , 0.09747455, -0.13116726, 1.2432434 ])"
"array([0.5408466 , 0.10346372, 0.57855067, 0.68666164, 0.01050236,\n",
" 0.64404705])"
]
},
"execution_count": 22,
"execution_count": 11,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"print([0, np.pi/10, np.pi/4, np.pi/2])\n",
"print(pol_params)\n",
"results.x"
]
},
Expand Down Expand Up @@ -156,7 +168,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.8"
"version": "3.11.5"
}
},
"nbformat": 4,
Expand Down
51 changes: 32 additions & 19 deletions docs/notebooks/FullMuellerExample.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
"metadata": {},
"source": [
"# Dual Rotating Retarder Mueller Polarimetry\n",
"So you want to measure a Mueller matrix, that's neat. To do so we use the Dual Rotating Retarder Polarimeter (DRRP). This consists of:\n",
"In order to measure how a system transforms the polarization state of light, we can measure the system's Mueller Matrix. There are a host of different methods by which we can do so. In this tutorial, we review the popular Dual Rotating Retarder Polarimeter (DRRP) for full Mueller matrix polarimetry. This consists of:\n",
"\n",
"- a source\n",
"- a linear polarizer at a fixed angle\n",
"- a quarter-wave plate that can rotate\n",
Expand All @@ -26,21 +27,21 @@
"- a linear polarizer at a fixed angle\n",
"- a detector to measure power\n",
"\n",
"The theory of this polarimeter is covered in Chipman, and also Azzam, but is reproduced in brief here. Our first polarizer/waveplate pair forms our *polarization state generator* (PSG). The latter pair forms our *polarization state analyzer* (PSA). The first waveplate must rotate $N_{meas} > 16$ times at some angular step size $\\delta\\theta$. The second waveplate must also rotate $N_{meas}$ times, but at five times the step size $5\\delta\\theta$.\n",
"The theory of this polarimeter is covered in Chipman, and also Azzam, but is reproduced in brief here. Our first polarizer/waveplate pair forms our *polarization state generator* (PSG). The latter pair forms our *polarization state analyzer* (PSA). The first waveplate must rotate $N_{meas} > 16$ times at some angular step size $\\delta\\theta$ to completely determine the Mueller matrix. The second waveplate must also rotate $N_{meas}$ times, but at five times the step size $5\\delta\\theta$.\n",
"\n",
"For each $\\delta\\theta$ we record the power on the detector. We next must understand that detectors can only measure power! This means, that it effectively measures the first row of the PSA matrix and the first column of the PSG matrix. The reason for that is revealed in the mueller calculus, which I've left as an exercise to the reader below*\n",
"For each $\\delta\\theta$ we record the power on the detector. We next must understand that detectors can only measure power! This means, that it effectively measures the first row of the PSA matrix and the first column of the PSG matrix. The reason for that is revealed in the Mueller calculus, which I've left as an exercise to the reader below*\n",
"\n",
"$$\\mathbf{S}_{out} = [\\mathbf{PSA}] \\mathbf{M}_{system} [\\mathbf{PSG}] \\mathbf{S_{0}}$$\n",
"\n",
"For each measurement iteration we compute our PSA and PSG, and then store those results in our polarimetric data reduction matrix $\\mathbf{W}$. This is an $N_{meas} \\times 16$ matrix that contains what our PSA and PSG are doing. $\\mathbf{W}$ is given by:\n",
"\n",
"$$\\mathbf{W} = [\\mathbf{PSA}_{0,j}] \\otimes [\\mathbf{PSG}_{i,0}]$$\n",
"\n",
"Where $i,j$ are the row and column indices, respectively, and $\\otimes$ is the Kronecker product. For each measurement, we record the power on the detector and store it in a vector that is $N_{meas}$ long, called $\\mathbf{P}$, and we computpe the unraveled mueller matrix using the following relation:\n",
"Where $i,j$ are the row and column indices, respectively, and $\\otimes$ is the Kronecker product. For each measurement, we record the power on the detector and store it in a vector that is $N_{meas}$ long, called $\\mathbf{P}$, and we computpe the unraveled Mueller matrix using the following relation:\n",
"\n",
"$$\\mathbf{M}_{meas} = \\mathbf{W}^{-1}\\mathbf{P}$$\n",
"\n",
"For the case where $N_{meas} > 16$, you must use the moore-penrose pseudo inverse to invert $\\mathbf{W}$. Now that the theory is out of the way, let's test it out!\n",
"For the case where $N_{meas} > 16$, you must use the moore-penrose pseudo inverse to invert $\\mathbf{W}$. Now that the theory is out of the way, let's test it out! We begin by constructing a random Mueller matrix composed of a polarizer and retarder:\n",
"\n",
"*aren't I the worst?"
]
Expand All @@ -53,18 +54,17 @@
{
"data": {
"text/plain": [
"array([[5.00000000e-01, 4.99915695e-01, 9.18137207e-03, 0.00000000e+00],\n",
" [4.01478997e-01, 4.01411304e-01, 7.37225609e-03, 0.00000000e+00],\n",
" [1.98600791e-02, 1.98567305e-02, 3.64685551e-04, 0.00000000e+00],\n",
" [2.97355330e-01, 2.97305193e-01, 5.46025985e-03, 0.00000000e+00]])"
"array([[ 0.5 , 0.09029522, 0.49177919, 0. ],\n",
" [ 0.11102659, 0.02005034, 0.10920113, 0. ],\n",
" [ 0.48566558, 0.08770656, 0.47768045, 0. ],\n",
" [-0.0424505 , -0.00766615, -0.04175255, 0. ]])"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"# Start by generating a random mueller matrix composed of a polarizer and retarder\n",
"M_to_measure = linear_retarder(np.random.random(),np.random.random()) @ linear_polarizer(np.random.random())\n",
"display(M_to_measure)"
]
Expand All @@ -75,14 +75,26 @@
"metadata": {},
"source": [
"## Performing a simulated measurement\n",
"This mode of `full_mueller_polarimetry` uses an internal simulator where the Mueller matrix is known, and is largely useful for 1) making sure the algorithm works and 2) testing the theoretical limits of the technique. We can provide it with the matrix above and it carries out the method described earlier. Let's see how it does! We begin with the minimum number of measurements, 16."
"In `katsu.polarimetry` we have This mode of `full_mueller_polarimetry` uses an internal simulator where the Mueller matrix is known, and is largely useful for 1) making sure the algorithm works and 2) testing the theoretical limits of the technique. We can provide it with the matrix above and it carries out the method described earlier. Let's see how it does! We begin with the minimum number of measurements, 16."
]
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 3,
"metadata": {},
"outputs": [],
"outputs": [
{
"ename": "TypeError",
"evalue": "full_mueller_polarimetry() missing 2 required positional arguments: 'power' and 'angular_increment'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[3], line 3\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# katsu has a simulator built-in, so let's try use that\u001b[39;00m\n\u001b[1;32m 2\u001b[0m thetas \u001b[38;5;241m=\u001b[39m np\u001b[38;5;241m.\u001b[39mlinspace(\u001b[38;5;241m0\u001b[39m,np\u001b[38;5;241m.\u001b[39mpi,\u001b[38;5;241m16\u001b[39m) \u001b[38;5;66;03m# 16 measurements between 0 and pi\u001b[39;00m\n\u001b[0;32m----> 3\u001b[0m M_out \u001b[38;5;241m=\u001b[39m full_mueller_polarimetry(thetas,Min\u001b[38;5;241m=\u001b[39mM_to_measure)\n",
"\u001b[0;31mTypeError\u001b[0m: full_mueller_polarimetry() missing 2 required positional arguments: 'power' and 'angular_increment'"
]
}
],
"source": [
"# katsu has a simulator built-in, so let's try use that\n",
"thetas = np.linspace(0,np.pi,16) # 16 measurements between 0 and pi\n",
Expand Down Expand Up @@ -283,12 +295,13 @@
"- 1) Katsu assumes that the mueller matrix is measured w.r.t. the axis of the linear polarizers as horizontal\n",
"- 2) Katsu assumes that the linear polarizers are parallel\n",
"- 3) Katsu assumes that the fast-axis of the quarter-wave plates begin parallel to the polarizers\n",
"- 4) Katsu operates on scalar powers, meaning that it can only presently process one pixel at a time\n",
"\n",
"These changes are easy to implement, it's just worth noting that they are not present in the codebase yet!\n",
"\n",
"**name preliminary, still thinking of a better one"
"- 4) Katsu operates on scalar powers, meaning that it can only presently process one pixel at a time"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": []
}
],
"metadata": {
Expand All @@ -307,7 +320,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.8"
"version": "3.11.5"
},
"orig_nbformat": 4
},
Expand Down
45 changes: 19 additions & 26 deletions docs/notebooks/FullStokesExample.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions docs/notebooks/MuellerCalculus.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"$$\\mathbf{s} = [I, Q, U, V] = [S_{0}, S_{1}, S_{2}, S_{3}].$$\n",
"\n",
"$\\mathbf{s}$ is a 4-vector that contains the _Stokes parameters_ that describe the polarization of light. Intuitively, the parameters can be described by a set of measurements made using the following six power measurements:\n",
"\n",
" - $P_{H}$: A power measurement made with a linear polarizer oriented at $0^{\\circ}$\n",
" - $P_{V}$: A power measurement made with a linear polarizer oriented at $90^{\\circ}$\n",
" - $P_{+}$: A power measurement made with a linear polarizer oriented at $45^{\\circ}$\n",
Expand Down

0 comments on commit 334a67e

Please sign in to comment.