mirror of
https://github.com/c-sooyoung/lemon-sandbox.git
synced 2026-11-16 04:07:59 +09:00
finding grain boundaries by fourier peak finding
This commit is contained in:
1 parent
53d30f714e
commit
d0c8d8723a
2 files changed
+1309
No files matched your search
@@ -0,0 +1,675 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "0e5a26dc",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-09-28T10:01:56.916372Z",
|
||||
"iopub.status.busy": "2026-09-28T10:01:56.916287Z",
|
||||
"iopub.status.idle": "2026-09-28T10:01:57.192257Z",
|
||||
"shell.execute_reply": "2026-09-28T10:01:57.191949Z"
|
||||
}
|
||||
},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import numpy as np\n",
|
||||
"from matplotlib import pyplot as plt, colormaps\n",
|
||||
"import tifffile"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "7b1d6105",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-09-28T10:01:57.193312Z",
|
||||
"iopub.status.busy": "2026-09-28T10:01:57.193168Z",
|
||||
"iopub.status.idle": "2026-09-28T10:01:57.359007Z",
|
||||
"shell.execute_reply": "2026-09-28T10:01:57.358776Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(30, 755, 755)"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"obj = tifffile.imread(\"./obj_phase_roi_Niter120.tiff\")\n",
|
||||
"obj.shape"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 25,
|
||||
"id": "298c10f5",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Pixel size: 0.24413245033112582 Angstrom\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"ROI_LENGTH_A = 0.36 * 512\n",
|
||||
"PIXEL_SIZE_A = ROI_LENGTH_A / obj.shape[-1]\n",
|
||||
"print(f\"Pixel size: {PIXEL_SIZE_A} Angstrom\")\n",
|
||||
"LAYER_DISTANCE_A = 10"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "6131af01",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Task 1 — estimate and remove the global column tilt\n",
|
||||
"\n",
|
||||
"Successive $z$ slices are registered with sub-pixel phase correlation after removing the slowly varying background and tapering the image edges. The cumulative registration shifts are fitted by a straight line; its slope is the global correction vector per slice. The physical column drift is the negative of that vector.\n",
|
||||
"\n",
|
||||
"The TIFF does not contain a physical $z$ spacing, so the result below is reported in lateral pixels per slice. The in-plane azimuth uses image coordinates: $+x$ is right, $+y$ is down, and angles increase clockwise."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 27,
|
||||
"id": "633a13a8",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-09-28T10:01:57.359991Z",
|
||||
"iopub.status.busy": "2026-09-28T10:01:57.359908Z",
|
||||
"iopub.status.idle": "2026-09-28T10:02:00.049288Z",
|
||||
"shell.execute_reply": "2026-09-28T10:02:00.048880Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Alignment correction (dy, dx): [0.19235818 0.05482759] pixels/slice = [4.69608743 1.33851936] mrad\n",
|
||||
"Column drift (dy, dx): [-0.19235818 -0.05482759] pixels/slice = [-4.69608743 -1.33851936] mrad\n",
|
||||
"Drift magnitude: 0.2000 pixels/slice\n",
|
||||
"Directed azimuth: 254.09° from +x\n",
|
||||
"Undirected tilt axis: 74.09° from +x\n",
|
||||
"Drift over the full stack: [-5.57838727 -1.59000007] pixels = [-1.36186535 -0.38817061] Angstrom\n",
|
||||
"Overlap crop (y, x): (slice(3, 752, None), slice(1, 754, None))\n",
|
||||
"fixed_image.shape: (749, 753)\n",
|
||||
"Saved: fixed_cropped_projection.tiff\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from scipy.ndimage import gaussian_filter, shift as ndi_shift\n",
|
||||
"from scipy.signal.windows import tukey\n",
|
||||
"from skimage.registration import phase_cross_correlation\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def estimate_global_tilt(volume, upsample_factor=20, highpass_sigma=6.0):\n",
|
||||
" \"\"\"Return the global alignment shift (dy, dx) per z slice.\"\"\"\n",
|
||||
" stack = np.asarray(volume, dtype=np.float32)\n",
|
||||
" if stack.ndim != 3 or stack.shape[0] < 2:\n",
|
||||
" raise ValueError(\"volume must have shape (z, y, x) with at least two slices\")\n",
|
||||
"\n",
|
||||
" # Keep atom-scale contrast and suppress edge discontinuities before FFT\n",
|
||||
" # registration. Registering neighbours avoids lattice-period ambiguity.\n",
|
||||
" highpass = stack - gaussian_filter(\n",
|
||||
" stack, sigma=(0, highpass_sigma, highpass_sigma)\n",
|
||||
" )\n",
|
||||
" edge_window = np.outer(\n",
|
||||
" tukey(stack.shape[1], alpha=0.2),\n",
|
||||
" tukey(stack.shape[2], alpha=0.2),\n",
|
||||
" ).astype(np.float32)\n",
|
||||
" registration_stack = highpass * edge_window[None, :, :]\n",
|
||||
"\n",
|
||||
" step_shifts = np.array(\n",
|
||||
" [\n",
|
||||
" phase_cross_correlation(\n",
|
||||
" registration_stack[z - 1],\n",
|
||||
" registration_stack[z],\n",
|
||||
" upsample_factor=upsample_factor,\n",
|
||||
" normalization=None,\n",
|
||||
" )[0]\n",
|
||||
" for z in range(1, stack.shape[0])\n",
|
||||
" ]\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" cumulative_shifts = np.vstack(\n",
|
||||
" (np.zeros(2), np.cumsum(step_shifts, axis=0))\n",
|
||||
" )\n",
|
||||
" z_coordinates = np.arange(stack.shape[0], dtype=float)\n",
|
||||
" alignment_per_slice = np.array(\n",
|
||||
" [\n",
|
||||
" np.polyfit(z_coordinates, cumulative_shifts[:, component], 1)[0]\n",
|
||||
" for component in range(2)\n",
|
||||
" ]\n",
|
||||
" )\n",
|
||||
" fitted_shifts = (\n",
|
||||
" z_coordinates - z_coordinates.mean()\n",
|
||||
" )[:, None] * alignment_per_slice\n",
|
||||
" return alignment_per_slice, fitted_shifts, step_shifts, cumulative_shifts\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def align_and_crop(volume, fitted_shifts):\n",
|
||||
" \"\"\"Apply (dy, dx) shifts and crop to the overlap shared by all slices.\"\"\"\n",
|
||||
" stack = np.asarray(volume, dtype=np.float32)\n",
|
||||
" aligned = np.stack(\n",
|
||||
" [\n",
|
||||
" ndi_shift(\n",
|
||||
" stack[z],\n",
|
||||
" shift=tuple(fitted_shifts[z]),\n",
|
||||
" order=3,\n",
|
||||
" mode=\"constant\",\n",
|
||||
" cval=0.0,\n",
|
||||
" prefilter=True,\n",
|
||||
" )\n",
|
||||
" for z in range(stack.shape[0])\n",
|
||||
" ]\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" dy, dx = fitted_shifts.T\n",
|
||||
" y0 = max(0, int(np.ceil(dy.max())))\n",
|
||||
" y1 = min(stack.shape[1], stack.shape[1] + int(np.floor(dy.min())))\n",
|
||||
" x0 = max(0, int(np.ceil(dx.max())))\n",
|
||||
" x1 = min(stack.shape[2], stack.shape[2] + int(np.floor(dx.min())))\n",
|
||||
" if y1 <= y0 or x1 <= x0:\n",
|
||||
" raise ValueError(\"estimated shifts leave no common image area\")\n",
|
||||
" return aligned[:, y0:y1, x0:x1], (slice(y0, y1), slice(x0, x1))\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"alignment_dy_dx, fitted_shifts, step_shifts, cumulative_shifts = \\\n",
|
||||
" estimate_global_tilt(obj)\n",
|
||||
"fixed_stack, overlap_crop = align_and_crop(obj, fitted_shifts)\n",
|
||||
"fixed_image = fixed_stack.sum(axis=0)\n",
|
||||
"\n",
|
||||
"# A shift aligns the current slice to the preceding slice, so the actual\n",
|
||||
"# movement of a column as z increases has the opposite sign.\n",
|
||||
"tilt_dy_dx = -alignment_dy_dx\n",
|
||||
"tilt_magnitude = np.hypot(*tilt_dy_dx)\n",
|
||||
"tilt_azimuth = np.degrees(np.arctan2(tilt_dy_dx[0], tilt_dy_dx[1])) % 360\n",
|
||||
"tilt_axis = tilt_azimuth % 180\n",
|
||||
"total_tilt_dy_dx = tilt_dy_dx * (obj.shape[0] - 1)\n",
|
||||
"\n",
|
||||
"print(f\"Alignment correction (dy, dx): {alignment_dy_dx} pixels/slice = {alignment_dy_dx * PIXEL_SIZE_A / LAYER_DISTANCE_A * 1000} mrad\")\n",
|
||||
"print(f\"Column drift (dy, dx): {tilt_dy_dx} pixels/slice = {tilt_dy_dx * PIXEL_SIZE_A / LAYER_DISTANCE_A * 1000} mrad\")\n",
|
||||
"print(f\"Drift magnitude: {tilt_magnitude:.4f} pixels/slice\")\n",
|
||||
"print(f\"Directed azimuth: {tilt_azimuth:.2f}° from +x\")\n",
|
||||
"print(f\"Undirected tilt axis: {tilt_axis:.2f}° from +x\")\n",
|
||||
"print(f\"Drift over the full stack: {total_tilt_dy_dx} pixels = {total_tilt_dy_dx * PIXEL_SIZE_A} Angstrom\")\n",
|
||||
"print(f\"Overlap crop (y, x): {overlap_crop}\")\n",
|
||||
"print(f\"fixed_image.shape: {fixed_image.shape}\")\n",
|
||||
"\n",
|
||||
"# Return a reusable image variable and an on-disk float32 TIFF.\n",
|
||||
"fixed_image_path = \"fixed_cropped_projection.tiff\"\n",
|
||||
"tifffile.imwrite(fixed_image_path, fixed_stack.astype(np.float32))\n",
|
||||
"print(f\"Saved: {fixed_image_path}\")"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 4,
|
||||
"id": "1e9c2b93",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-09-28T10:02:00.050342Z",
|
||||
"iopub.status.busy": "2026-09-28T10:02:00.050194Z",
|
||||
"iopub.status.idle": "2026-09-28T10:02:00.339494Z",
|
||||
"shell.execute_reply": "2026-09-28T10:02:00.339168Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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vFOx2WxKQ0ODEggExL0KCgqUrKwspbi4WLn33nuVgwcPKseOHVMmJyeVvLw8paCgQPn1r3+t7N+/X/nOd76jmEwm5aabbnrd57311lsVAMpHPvIR5fHHH1f+/Oc/Kz/96U+VW2+9VZzz/e9/XwGgXHfddcozzzyjPPDAA0pxcbHicDiUnp4ecd6NN96oGAwGpbCwUPnBD36gHDhwQNm7d6+iKIoCQMnJyVHq6uqUhx56SHn++eeVtrY2pb29XXE4HEptba3ywAMPKH/+85+Vm2++WdFqtcptt90mru3xeJSamhrFZrMpd9xxh7J3717liSeeUL7yla8ozz//vBIIBJQ9e/YoAJTPfOYzop/7+vrixpU6wRt9LqPRqFRVVSl33nmnsn//fuVb3/qWotFoztJnVKhQoeLvBT6fT3E4HMr69esVRVGUu+++WwGg/Pa3vxXnNDU1KZ/5zGcUAMqePXuUI0eOKENDQ6+5nl7oun3w4EGx9n/sYx9Tnn76aWX37t2Ky+U6Z3vD4bCyY8cORa/XK//2b/+mPPvss8rTTz+t3HLLLcrDDz+sKIrylsjN12rX7373O0Wj0Sgf+tCHlF27dil/+tOflKuuukrR6XTK/v37xbVPnTql2O12pbCwULnrrruUAwcOKL///e+Vj3/844rH41FmZmaEjPnFL35xli154403KgUFBeJ6b/S5cnNzlerqauWBBx5Q9u7dq1xzzTUKAOXw4cOvOy+Ki4uVj3/843HHzjUPaMuutNNjsZhyxRVXKMnJycrQ0JCiKIpy7733KgCUu+++W5x3oX25f/9+RafTKe95z3uUXbt2KY8//riyfv16JT8//yy+4vVwLo6D/bVq1Srl4YcfVp599lll48aNisFgUL71rW8pW7ZsUXbt2qU8+eSTSnl5uZKRkSH0N0VRlMcff1z51re+pTz55JPK4cOHlUceeUTZvn27kpaWFseRNDc3K2azWamrq1MeeeQR5emnn1auuOIKpbCw8Cz943vf+56i0WiUT3/608ru3buVXbt2KZs3b1ZsNpvS3t4e1/5IJKLY7XblX//1X99QX6j420El0FWoeIdgampKAaBce+21Z30XiUSUcDgsPrFYTHy3Urh0dnYqAJQvfOELcdd49dVXFQDKLbfcIo5t375dAaD88Y9/jDv3n/7pnxStVqsMDw+fs63RaFQJh8PKHXfcoaSkpMS150IIdEVRlJqamnOedyEEuqIoypVXXhmnrLwebrzxRsVms511XCbQFUVRjh8/ftb9VahQoULFOw+UHSs/JpNJ+eUvfxl37sMPP6wAUJ544om445QJ8vnnkickKH784x/HHX/00UcVAMpvfvMbcaygoEDR6XRKd3d33LmLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1500x500 with 3 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Registration diagnostic and before/after comparison.\n",
|
||||
"z_coordinates = np.arange(obj.shape[0])\n",
|
||||
"fitted_path = (\n",
|
||||
" z_coordinates[:, None] * alignment_dy_dx\n",
|
||||
" + (cumulative_shifts - z_coordinates[:, None] * alignment_dy_dx).mean(axis=0)\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"fig, axes = plt.subplots(1, 3, figsize=(15, 5), constrained_layout=True)\n",
|
||||
"axes[0].plot(z_coordinates, cumulative_shifts[:, 1], \"o\", ms=3, label=\"measured dx\")\n",
|
||||
"axes[0].plot(z_coordinates, cumulative_shifts[:, 0], \"o\", ms=3, label=\"measured dy\")\n",
|
||||
"axes[0].plot(z_coordinates, fitted_path[:, 1], \"-\", lw=1.5, label=\"linear dx fit\")\n",
|
||||
"axes[0].plot(z_coordinates, fitted_path[:, 0], \"-\", lw=1.5, label=\"linear dy fit\")\n",
|
||||
"axes[0].set(xlabel=\"z slice\", ylabel=\"alignment shift (pixels)\", title=\"Global tilt fit\")\n",
|
||||
"axes[0].legend(fontsize=8)\n",
|
||||
"axes[0].grid(alpha=0.2)\n",
|
||||
"\n",
|
||||
"original_cropped = obj[(slice(None),) + overlap_crop].sum(axis=0)\n",
|
||||
"display_limits = np.percentile(\n",
|
||||
" np.concatenate((original_cropped.ravel(), fixed_image.ravel())), (1, 99.8)\n",
|
||||
")\n",
|
||||
"for ax, image, title in zip(\n",
|
||||
" axes[1:],\n",
|
||||
" (original_cropped, fixed_image),\n",
|
||||
" (\"Before correction\", \"After correction (fixed_image)\"),\n",
|
||||
"):\n",
|
||||
" ax.imshow(image, cmap=\"gray\", vmin=display_limits[0], vmax=display_limits[1])\n",
|
||||
" ax.set_title(title)\n",
|
||||
" ax.set_axis_off()\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "markdown",
|
||||
"id": "1463886a",
|
||||
"metadata": {},
|
||||
"source": [
|
||||
"## Per-layer reciprocal-lattice domain coloring\n",
|
||||
"\n",
|
||||
"This ports the calibrated Si $[110]$ reciprocal-grid method from `grain-boundaries.ipynb` to the tilt-corrected stack. The fit uses the summed stack for a high-SNR reference, greedily removes reflections belonging to each fitted orientation, and stops when the residual grid has fewer than 12 matched reflections or less than 15% of the dominant score.\n",
|
||||
"\n",
|
||||
"The source notebook's calibration, $92\\,\\mathrm{Å}/379$ pixels, is retained. Each accepted orientation gets a rotated ideal-grid mask. Reciprocal-space pixels shared by multiple masks are removed because they do not distinguish the grains; real-space atoms supported by more than one orientation still receive an additive mixture of the corresponding colors."
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 34,
|
||||
"id": "b825da5d",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-09-28T10:02:00.341856Z",
|
||||
"iopub.status.busy": "2026-09-28T10:02:00.341761Z",
|
||||
"iopub.status.idle": "2026-09-28T10:02:12.009745Z",
|
||||
"shell.execute_reply": "2026-09-28T10:02:12.009493Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Fitted reciprocal scale correction: 0.9950\n",
|
||||
"Orientation 1: 72.50°, score= 407.07 (1.000 of dominant), matched reflections=36\n",
|
||||
"Orientation 2: 2.00°, score= 165.96 (0.408 of dominant), matched reflections=20\n",
|
||||
"Orientation 3: 143.50°, score= 136.48 (0.335 of dominant), matched reflections=14\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAABN4AAAJjCAYAAADTddy3AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsvXecJVWZ8P89lW++nfN0z/REJjLADEMOS4aXIAprIqqo+C7qqqhIUBRF3QVxWfWngu+CLCsmMKEgIEoamGGGSUzs6enu6Zxuqnx+f/R0S0+ABgm61PfzuZ+ZPnXq1FOnzr311FNPEFJKSURERERERERERERERERERERERMTrivJWCxAREREREREREREREREREREREfG/kcjwFhERERERERERERERERERERER8QYQGd4iIiIiIiIiIiIiIiIiIiIiIiLeACLDW0RERERERERERERERERERERExBtAZHiLiIiIiIiIiIiIiIiIiIiIiIh4A4gMbxEREREREREREREREREREREREW8AkeEtIiIiIiIiIiIiIiIiIiIiIiLiDSAyvEVERERERERERERERERERERERLwBRIa3iIiIiIiIiIiIiIiIiIiIiIiIN4DI8PZ3yp133okQgmefffYV+1588cW0tLS8ofJcf/31CCGm1PfBBx/k5JNPpr6+HtM0qa+v57jjjuOrX/3qpH4tLS1cfPHFLztWW1sbQoiJz3333TexLZfL8elPf5qTTz6ZqqoqhBBcf/31+x3nz3/+M5dffjmHHHIIpmkihKCtre2Ax73tttuYO3cupmkyffp0brjhBjzPm9TnlltumSRbf3//y57L68Gjjz6KEIJHH330Ffu+nuuira2NM844g/LycoQQXHXVVRPX5s4775zo98QTT3D99dczPDy8zxi33377pL6vJy937cfp6uri+uuv5/nnn39DZIgY49Ws0anwRq6bl9LS0sKZZ575mvY97rjjJv0WvPSzbt064K/zsr/P+eefz8UXX3zA7S/9vNJvZkRERMRbyVNPPcU73/lO6urqMAyD2tpazj//fJ588slXNc6r0TtfK8Vikeuvv/5vul+9nG7xZpzD3xOv9/3/zR7/reZ/2/lN5Tnv743jjjuOBQsWvNViRPwvRXurBYj438V3vvMdPvzhD/OOd7yDb3/725SXl7Nr1y6eeOIJ7rvvPq6++uqJvj//+c9Jp9NTGveaa67hjDPOYPbs2RNtAwMDfO9732Px4sWcc845fP/73z/g/g8//DAPPfQQBx98MOl0+mVval/+8pf5whe+wNVXX83JJ5/MypUrueaaa+js7OR73/veRL8LL7yQww8/nO9///v84Ac/mNJ5/K0sXbqUJ598koMOOuhNOd44H//4x3n66af54Q9/SG1tLXV1ddTW1vLkk0/S2to60e+JJ57ghhtu4OKLLyabzU4a4/bbb6eysvItuwl3dXVxww030NLSwpIlS94SGSJePW/1upkqM2bM4O67796n/aXfD4CvfOUrHH/88ZPaKioqUBSFK664YqJt1apVfPSjH92nf1VV1esseURERMTrw2233cZVV13FsmXLuPnmm2lubqa9vZ3/+I//4KijjuLWW2/lyiuvnNJYl19+OaeeeuobKm+xWOSGG24Axh64Xwsvp1u8GefwduKt0oHfLP63n19ExNudyPAW8bpy0003ccwxx0zyTAN43/veRxiGk9oOPvjgKY/b2trK4YcfPqmtubmZoaGhCW+zlzO8feELX+C6664D4Bvf+MYBDW8DAwPceOONfOADH+ArX/kKMKaMeZ7HNddcw1VXXTVxQ6ytraW2tpbf/e53Uz6P14rneQghSKfT+8zDm8G6detYtmwZ55xzzqT2t0KWiIi/R2Kx2JS+D7NmzTpgv5ca6WzbfsX+EREREX8v/OUvf+Gqq67i9NNP5+c//zma9tdHjAsvvJBzzz2Xf/mXf+Hggw/myCOPPOA4xWKReDxOY2MjjY2Nb4bobxj/G87h74m3SgfeH+N6+UvX+d/K631+Ukps2yYWi71uY0ZERLx2olDTfzDuvPNO5syZg2mazJs3j//3//7ffvu5rsuNN944ES5ZVVXFJZdcQl9f36R+9957LyeffDJ1dXXEYjHmzZvH1VdfTaFQeE3yDQwMUFdXt99tijJ5uf2tLsjjoVdTYe9jH4jf/e532LbNJZdcMqn9kksuQUrJL37xi1cr5gRSSr7yla/Q3NyMZVkceuih/OEPf+C4446b9KZ13NX8v/7rv/jkJz9JQ0MDpmmydevWA7qhT3Vd/Od//ieLFy8mmUySSqWYO3cun/vc5w4o8/jxtm7dym9/+9uJOW9ra9sn1PT666/nU5/6FADTp0+f6Pvoo4/S0tLC+vXreeyxxybaXxoGOzo6yr/+678yffp0DMOgoaGBq666ap91ODo6ygc+8AEqKipIJpOceuqpbN68+RXn/tFHH+Wwww4Dxq7luAwvDU+9//77WbFiBfF4nFQqxUknnTSl0JgwDLnxxhuZM2cOsViMbDbLokWLuPXWWyf6HCjsd39hKEIIrrzySu64446JMQ899FCeeuoppJR8/etfZ/r06SSTSU444QS2bt06af9xN/knn3ySI444glgsRktLC3fccQcAv/71r1m6dCnxeJyFCxfu13C8ZcsW3v3ud1NdXT2xpv7jP/5jn36bNm3i1FNPJR6PU1lZyRVXXEEul3vFORtn+/btXHjhhRNh6TU1NZx44okTITsvt25s2+aTn/wkS5YsIZPJUF5ezooVK/jlL3+5z3HCMOS2225jyZIlE9fo8MMP5/77739Z+W6//XY0TZsw2kdERERE7J+bbroJIQT/+Z//uY8xQtM0br/9doQQk9KOjN8DV61axfnnn09ZWdnEC4gDhWnee++9rFixgkQiQTKZ5JRTTmH16tWT+lx88cUkk0m2bt3K6aefTjKZpKmpiU9+8pM4jgOMpdAY9yC+4YYb9gnn37p1K5dccgmzZs0iHo/T0NDAWWedxQsvvDBxnFfSLfZ3DmEYcvPNN0/o59XV1bz//e+no6NjUr/xe/nKlSs5+uijicfjzJgxg69+9av7vMg+EFN5FvjqV7+Koig88MAD+8xhPB6fdL6bNm3in//5n6mpqcE0TaZNm8b73//+iTndH3vruC8df2+96JV01L114PGUL3vrQQCf+cxnMAxjUhqYhx56iBNPPJF0Ok08HufII4/k4YcfPqDsex93f3r5qxn3leZvfzr++Fpev349J554IolEgqqqKq688kqKxeKk8cf1x+985zvMmzcP0zT50Y9+BIyl3DnxxBNJpVLE43GOOOIIfv3rX+8jY2dnJx/84AdpamrCMAzq6+s5//zz6enpAV6d7jVV/la99w9/+ANnn302jY2NWJbFzJkz+dCHPrRPCqC+vr6Jcxv/Phx55JE89NBDLyvfz3/+c+LxOJdffjm+7wPwk5/8hOXLl5PJZCa+m5deeulrnoOItweRx9s/EHfeeSeXXHIJZ599Nt/85jcZGRnh+uuvx3GcSYalMAw5++yzefzxx/n0pz/NEUccwc6dO7nuuus47rjjePbZZyfefmzZsoXTTz+dq666ikQiwaZNm/ja177GM888wx//+MdXLeOKFSv46U9/yvXXX8+5557LggULUFX1dZuDN5rxfEwLFy6c1F5XV0dlZeXE9tfC5z//eW666SY++MEPct5557Fr1y4uv/xyPM+bFEI7zmc/+1lWrFjBd77zHRRFobq6mu7u7n36TXVd/Pd//zcf+chH+NjHPsY3vvENFEVh69atbNiw4YAyj7u9n3vuubS2tvKNb3xjYj527949qe/ll1/O4OAgt912Gz/72c8mDLAHHXQQP//5zzn//PPJZDLcfvvtAJimCYy93T722GPp6Ojgc5/7HIsWLWL9+vVce+21vPDCCzz00EMIIZBScs455/DEE09w7bXXcthhh/GXv/yF00477RXnfunSpdxxxx1ccsklE2HLwMSb6B//+Me85z3v4eSTT+aee+7BcRxuvvlmjjvuOB5++GGOOuqoA4598803c/3113PNNddwzDHH4HkemzZt2m+eu6nyq1/9itWrV/PVr34VIQSf+cxnOOOMM7jooovYvn073/72txkZGeETn/gE73jHO3j++ecnKffd3d1ccsklfPrTn6axsZHbbruNSy+9lF27dnHffffxuc99jkwmwxe/+EXOOecctm/fTn19PQAbNmzgiCOOYNq0aXzzm9+ktraWBx98kP/7f/8v/f39E0aonp4ejj32WHRd5/bbb6empoa77757ymFEAKeffjpBEHDzzTczbdo0+vv7eeKJJybm7uXWjeM4DA4O8q//+q80NDTgui4PPfQQ5513HnfccQfvf//7J45z8cUXc9ddd3HZZZfxxS9+EcMwWLVq1QHzPEop+dSnPsW3vvUtvv/970/5BcG4QjaOoij7GP3DMNyn3+v5xjwiIiLizSYIAh555BEOPfTQA3p4NTU1ccghh/DHP/6RIAgm6YbnnXceF154IVdcccXLvvj9yle+wjXXXDNxL3ddl69//escffTRPPPMM5NC9DzP4//8n//DZZddxic/+Un+9Kc/8aUvfYlMJsO1115LXV0dv/vd7zj11FO57LLLuPzyy4G/hvN3dXVRUVHBV7/6VaqqqhgcHORHP/oRy5cvZ/Xq1cyZM+cVdYv98eEPf5jvfe97XHnllZx55pm0tbXxhS98gUcffZRVq1ZRWVk50be7u5v3vOc9fPKTn+S6667j5z//OZ/97Gepr6+fdI/bH1N9FvjMZz7D448/zkUXXcTq1atpbm7mjjvu4Ec/+hHf//73J/ThNWvWcNRRR1FZWckXv/hFZs2axe7du7n//vtxXXfi3vxaeS066nvf+14+85nPcOedd3LjjTdOtAdBwF133cVZZ501MZ933XUX73//+zn77LPLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1300x600 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from scipy.ndimage import maximum_filter\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Calibration and fit controls inherited from grain-boundaries.ipynb.\n",
|
||||
"pixel_size_A = PIXEL_SIZE_A\n",
|
||||
"d_004_A = 1.358\n",
|
||||
"a_si_A = 4 * d_004_A\n",
|
||||
"match_tolerance_px = 6\n",
|
||||
"mask_radius_px = 8\n",
|
||||
"minimum_matched_reflections = 12\n",
|
||||
"minimum_relative_score = 0.15\n",
|
||||
"\n",
|
||||
"coloring_volume = np.asarray(fixed_stack, dtype=np.float32)\n",
|
||||
"fit_image = coloring_volume[-1]\n",
|
||||
"height, width = fit_image.shape\n",
|
||||
"centre_y, centre_x = (np.asarray(fit_image.shape) - 1) / 2\n",
|
||||
"yy, xx = np.ogrid[:height, :width]\n",
|
||||
"\n",
|
||||
"fit_fft = np.fft.fftshift(np.fft.fft2(fit_image))\n",
|
||||
"fit_spectrum = np.log1p(np.abs(fit_fft))\n",
|
||||
"radius_px = np.hypot(xx - centre_x, yy - centre_y)\n",
|
||||
"\n",
|
||||
"# Detect the strongest non-central local FFT maxima.\n",
|
||||
"local_maximum = maximum_filter(fit_spectrum, size=7, mode=\"wrap\")\n",
|
||||
"candidate_pixels = np.argwhere((fit_spectrum == local_maximum) & (radius_px > 8))\n",
|
||||
"candidate_strength = fit_spectrum[candidate_pixels[:, 0], candidate_pixels[:, 1]]\n",
|
||||
"strongest = np.argsort(candidate_strength)[-min(600, len(candidate_pixels)):]\n",
|
||||
"candidate_pixels = candidate_pixels[strongest]\n",
|
||||
"candidate_strength = candidate_strength[strongest]\n",
|
||||
"\n",
|
||||
"# Allowed diamond-cubic Si [110] reciprocal points. Coordinates are physical\n",
|
||||
"# reciprocal units (1/Å) along [1 -1 0] and [001].\n",
|
||||
"grid_hkls, grid_reciprocal_xy = [], []\n",
|
||||
"for h in range(-12, 13):\n",
|
||||
" for l in range(-12, 13):\n",
|
||||
" if h == 0 and l == 0:\n",
|
||||
" continue\n",
|
||||
" all_odd = (h % 2 != 0) and (l % 2 != 0)\n",
|
||||
" diamond_even = (h % 2 == 0) and (l % 2 == 0) and (l % 4 == 0)\n",
|
||||
" if all_odd or diamond_even:\n",
|
||||
" grid_hkls.append((h, -h, l))\n",
|
||||
" grid_reciprocal_xy.append((np.sqrt(2) * h / a_si_A, l / a_si_A))\n",
|
||||
"grid_hkls = np.asarray(grid_hkls)\n",
|
||||
"grid_reciprocal_xy = np.asarray(grid_reciprocal_xy)\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def projected_si110_grid(angle_deg, scale_factor):\n",
|
||||
" \"\"\"Return in-frame (hkl, [y, x]) sites for a rotated ideal grid.\"\"\"\n",
|
||||
" angle = np.deg2rad(angle_deg)\n",
|
||||
" rotation = np.array(\n",
|
||||
" [[np.cos(angle), -np.sin(angle)],\n",
|
||||
" [np.sin(angle), np.cos(angle)]]\n",
|
||||
" )\n",
|
||||
" reciprocal_xy = scale_factor * grid_reciprocal_xy @ rotation.T\n",
|
||||
" pixels = np.column_stack(\n",
|
||||
" (\n",
|
||||
" centre_y + reciprocal_xy[:, 1] * height * pixel_size_A,\n",
|
||||
" centre_x + reciprocal_xy[:, 0] * width * pixel_size_A,\n",
|
||||
" )\n",
|
||||
" )\n",
|
||||
" valid = (\n",
|
||||
" (pixels[:, 0] >= 7) & (pixels[:, 0] < height - 7)\n",
|
||||
" & (pixels[:, 1] >= 7) & (pixels[:, 1] < width - 7)\n",
|
||||
" )\n",
|
||||
" return grid_hkls[valid], pixels[valid]\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def reciprocal_grid_score(angle_deg, scale_factor, pixels, strengths):\n",
|
||||
" \"\"\"Score observed maxima near the specified ideal reciprocal grid.\"\"\"\n",
|
||||
" _, targets = projected_si110_grid(angle_deg, scale_factor)\n",
|
||||
" distance2 = ((targets[:, None, :] - pixels[None, :, :]) ** 2).sum(axis=2)\n",
|
||||
" nearest = np.argmin(distance2, axis=1)\n",
|
||||
" nearest_distance2 = distance2[np.arange(len(targets)), nearest]\n",
|
||||
" matches = nearest_distance2 <= match_tolerance_px ** 2\n",
|
||||
" score = np.sum(\n",
|
||||
" strengths[nearest[matches]]\n",
|
||||
" * (1 - nearest_distance2[matches] / match_tolerance_px ** 2)\n",
|
||||
" )\n",
|
||||
" return float(score), int(matches.sum())\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Fit the dominant detector rotation and a small scale correction.\n",
|
||||
"dominant_score, dominant_angle, best_scale, dominant_match_count = -np.inf, None, None, 0\n",
|
||||
"for scale in np.linspace(0.90, 1.10, 11):\n",
|
||||
" for angle in np.arange(0, 180, 1):\n",
|
||||
" score, match_count = reciprocal_grid_score(\n",
|
||||
" angle, scale, candidate_pixels, candidate_strength\n",
|
||||
" )\n",
|
||||
" if score > dominant_score:\n",
|
||||
" dominant_score, dominant_angle, best_scale, dominant_match_count = (\n",
|
||||
" score, angle, scale, match_count\n",
|
||||
" )\n",
|
||||
"for scale in np.linspace(best_scale - 0.015, best_scale + 0.015, 13):\n",
|
||||
" for angle in np.linspace(dominant_angle - 1, dominant_angle + 1, 41):\n",
|
||||
" score, match_count = reciprocal_grid_score(\n",
|
||||
" angle, scale, candidate_pixels, candidate_strength\n",
|
||||
" )\n",
|
||||
" if score > dominant_score:\n",
|
||||
" dominant_score, dominant_angle, best_scale, dominant_match_count = (\n",
|
||||
" score, angle, scale, match_count\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"# Repeatedly rotate the calibrated grid against residual FFT maxima.\n",
|
||||
"remaining_pixels = candidate_pixels.copy()\n",
|
||||
"remaining_strength = candidate_strength.copy()\n",
|
||||
"orientation_fits = []\n",
|
||||
"for orientation_index in range(8):\n",
|
||||
" best_residual = (-np.inf, None, 0)\n",
|
||||
" for angle in np.arange(0, 180, 0.25):\n",
|
||||
" score, match_count = reciprocal_grid_score(\n",
|
||||
" angle, best_scale, remaining_pixels, remaining_strength\n",
|
||||
" )\n",
|
||||
" if score > best_residual[0]:\n",
|
||||
" best_residual = (score, angle, match_count)\n",
|
||||
"\n",
|
||||
" score, angle, match_count = best_residual\n",
|
||||
" if (\n",
|
||||
" match_count < minimum_matched_reflections\n",
|
||||
" or score < minimum_relative_score * dominant_score\n",
|
||||
" ):\n",
|
||||
" break\n",
|
||||
" hkls, grid_pixels = projected_si110_grid(angle, best_scale)\n",
|
||||
" orientation_fits.append(\n",
|
||||
" {\n",
|
||||
" \"angle_deg\": float(angle),\n",
|
||||
" \"score\": float(score),\n",
|
||||
" \"relative_score\": float(score / dominant_score),\n",
|
||||
" \"matched_reflections\": int(match_count),\n",
|
||||
" \"hkls\": hkls,\n",
|
||||
" \"grid_pixels\": grid_pixels,\n",
|
||||
" }\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
" # Exclude all residual candidates close to this orientation's full grid.\n",
|
||||
" distance2 = (\n",
|
||||
" (remaining_pixels[:, None, :] - grid_pixels[None, :, :]) ** 2\n",
|
||||
" ).sum(axis=2)\n",
|
||||
" keep = distance2.min(axis=1) > (2 * match_tolerance_px) ** 2\n",
|
||||
" remaining_pixels = remaining_pixels[keep]\n",
|
||||
" remaining_strength = remaining_strength[keep]\n",
|
||||
"\n",
|
||||
"if len(orientation_fits) < 2:\n",
|
||||
" raise RuntimeError(\"Fewer than two statistically supported lattice orientations were found.\")\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"def reciprocal_disk_mask(points_yx, radius):\n",
|
||||
" \"\"\"Draw circular masks locally, avoiding a full-frame temporary per spot.\"\"\"\n",
|
||||
" mask = np.zeros((height, width), dtype=bool)\n",
|
||||
" integer_radius = int(np.ceil(radius))\n",
|
||||
" for y, x in points_yx:\n",
|
||||
" y0 = max(0, int(np.floor(y)) - integer_radius)\n",
|
||||
" y1 = min(height, int(np.floor(y)) + integer_radius + 2)\n",
|
||||
" x0 = max(0, int(np.floor(x)) - integer_radius)\n",
|
||||
" x1 = min(width, int(np.floor(x)) + integer_radius + 2)\n",
|
||||
" patch_y, patch_x = np.ogrid[y0:y1, x0:x1]\n",
|
||||
" mask[y0:y1, x0:x1] |= (\n",
|
||||
" (patch_y - y) ** 2 + (patch_x - x) ** 2 <= radius ** 2\n",
|
||||
" )\n",
|
||||
" return mask\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"orientation_masks = np.stack(\n",
|
||||
" [\n",
|
||||
" reciprocal_disk_mask(fit[\"grid_pixels\"], mask_radius_px)\n",
|
||||
" for fit in orientation_fits\n",
|
||||
" ]\n",
|
||||
")\n",
|
||||
"\n",
|
||||
"# Reflections common to multiple grids carry no orientation information.\n",
|
||||
"shared_reciprocal_pixels = orientation_masks.sum(axis=0) > 1\n",
|
||||
"orientation_masks &= ~shared_reciprocal_pixels[None, :, :]\n",
|
||||
"\n",
|
||||
"grain_colors = np.array(\n",
|
||||
" [\n",
|
||||
" [0.10, 0.45, 1.00], # dominant: blue\n",
|
||||
" [1.00, 0.12, 0.05], # second: red\n",
|
||||
" [0.15, 1.00, 0.20], # third: green\n",
|
||||
" [1.00, 0.25, 0.90], # extra orientations, if statistically supported\n",
|
||||
" [1.00, 0.75, 0.05],\n",
|
||||
" [0.10, 1.00, 0.95],\n",
|
||||
" [0.75, 0.35, 1.00],\n",
|
||||
" [0.65, 1.00, 0.10],\n",
|
||||
" ],\n",
|
||||
" dtype=np.float32,\n",
|
||||
")[:len(orientation_fits)]\n",
|
||||
"\n",
|
||||
"print(f\"Fitted reciprocal scale correction: {best_scale:.4f}\")\n",
|
||||
"for index, fit in enumerate(orientation_fits, start=1):\n",
|
||||
" print(\n",
|
||||
" f\"Orientation {index}: {fit['angle_deg']:6.2f}°, \"\n",
|
||||
" f\"score={fit['score']:7.2f} \"\n",
|
||||
" f\"({fit['relative_score']:.3f} of dominant), \"\n",
|
||||
" f\"matched reflections={fit['matched_reflections']}\"\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"# Show the measured spectrum, fitted grids, and orientation-exclusive masks.\n",
|
||||
"fig, axes = plt.subplots(1, 2, figsize=(13, 6), constrained_layout=True)\n",
|
||||
"axes[0].imshow(\n",
|
||||
" fit_spectrum,\n",
|
||||
" cmap=\"magma\",\n",
|
||||
" vmin=np.percentile(fit_spectrum, 5),\n",
|
||||
" vmax=np.percentile(fit_spectrum, 99.9),\n",
|
||||
")\n",
|
||||
"axes[0].plot(candidate_pixels[:, 1], candidate_pixels[:, 0], \"w.\", ms=1, alpha=0.2)\n",
|
||||
"for fit, color in zip(orientation_fits, grain_colors):\n",
|
||||
" grid_pixels = fit[\"grid_pixels\"]\n",
|
||||
" axes[0].plot(\n",
|
||||
" grid_pixels[:, 1], grid_pixels[:, 0], \"+\", color=color, ms=4, mew=0.8,\n",
|
||||
" label=f\"{fit['angle_deg']:.2f}°\",\n",
|
||||
" )\n",
|
||||
"axes[0].set_title(\"Ideal Si [110] grids fitted to summed-stack FFT\")\n",
|
||||
"axes[0].legend(loc=\"upper right\", fontsize=8)\n",
|
||||
"\n",
|
||||
"mask_rgb = np.zeros((height, width, 3), dtype=np.float32)\n",
|
||||
"for mask, color in zip(orientation_masks, grain_colors):\n",
|
||||
" mask_rgb += mask[..., None] * color\n",
|
||||
"axes[1].imshow(np.clip(mask_rgb, 0, 1))\n",
|
||||
"axes[1].set_title(\"Orientation-exclusive reciprocal masks\")\n",
|
||||
"for axis in axes:\n",
|
||||
" axis.set_axis_off()\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 36,
|
||||
"id": "47070371",
|
||||
"metadata": {
|
||||
"execution": {
|
||||
"iopub.execute_input": "2026-09-28T10:02:12.012144Z",
|
||||
"iopub.status.busy": "2026-09-28T10:02:12.012061Z",
|
||||
"iopub.status.idle": "2026-09-28T10:02:15.084841Z",
|
||||
"shell.execute_reply": "2026-09-28T10:02:15.084581Z"
|
||||
}
|
||||
},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Saved (30, 749, 753, 3) RGB stack to /home/swim/sandbox/silicon-grain-boundaries/colored_grains_by_layer.tiff\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAAEFEAAAhWCAYAAAAqk/AiAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAuIwAALiMBeKU/dgABAABJREFUeJzs3XmcHUW9Pv6nqvucM2v2nUAS9kR2CBBANoGwCF4IXL8qAgoICIKCV1EREOWC1+V3ZRG4IKC4IfsOgomyBAgJYRMSCAnZEybJ7HO27qrfH9Xdp/ucPjNnlmQmk+f9egVmerqrq7fqqk9XVwuttQYRERERERERERERERERERERERERERERERERERERERER0VZO9ncGiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIj6AgdRICIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIqJBgYMoEBEREREREREREREREREREREREREREREREREREREREdGgwEEUiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiGhQ4CAKRERERERERERERERERERERERERERERERERERERERERDQocBAFIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIhoUOIgCEREREREREREREREREREREREREREREREREREREREREQ0KHESBiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiAYFDqJARERERERERERERERERERERERERERERERERERERERERIMCB1EgIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiokGBgygQERERERERERERERERERERERERERERERERERERERER0aDAQRSIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIaFDgIApERERERERERERERERERERERERERERERERERERERERENChwEAUiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiGhQ4iAIRERERERERERERERERERERERERERERERERERERERERDQocRIGIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIBgUOokBEREREREREREREREREREREREREREREREREREREREREgwIHUSAiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiKiQYGDKBARERERERERERERERERERERERERERERERERERERERHRoMBBFIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIhoUOAgCkREREREREREREREREREREREREREREREREREREREREQ0KHAQBSIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIaFDiIAhERERERERERERERERERERERERERERERERERERERERENChxEgYiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIgGBQ6iQERERERERERERERERERERERERERERERERERERERERESDAgdRICIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIqJBgYMoEBEREREREREREREREREREREREREREREREREREREREdGgwEEUiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiIiGhQ4CAKRERERERERERERERERERERERERERERERERERERERERDQocBAFIqIB6Mgjj4QQIvh35JFH9neWqJsmT54cOYbnnHPOZl/nP//5z8g6hRD45z//udnXS0RERERERAMb4wxbP8YZiIiIiIiIqK8wTrD1Y5yAiIiIiIiIBgrGGbZ+jDMQERHRYGb3dwaIiHpKa40PP/wQS5YswcqVK9Ha2opsNov6+noMHz4cI0aMwB577IHJkyf3d1aJiGgQUEph4cKFeO+99/Dpp58im82irq4OU6ZMwf7774+JEyf2dxaJiIiIqBcYZyAiov6yatUqvPPOO/jkk0/Q0tICpRTq6uowYcIE7LTTTpg2bRpSqVR/Z5OIiIhom8I4ARERbQtaW1vx73//Gx999BEaGxvR2tqK2tpaDB8+HNtttx2mT5+OoUOH9nc2iYiIiLZ6jDMQEREREfUPDqJARFuV9vZ2PPjgg3j44Yfxr3/9C83NzV0uM2rUKBx00EGYNWsWZs2ahSFDhmyBnBIR0WCxevVq/PKXv8R9992HjRs3lp1v3333xcUXX4xzzjkHlmVtwRwSERERUU8xzkBERP1l7dq1uPPOO/HnP/8Zixcv7nTeZDKJffbZB8cddxxOO+007Lvvvlsol0RERETbFsYJiIhoS3JdFx988AHmz58f/Hv77beRyWQi811zzTW49tpr+2Sdzc3NePbZZzFnzhzMmTMHH374YafzCyGwxx574KyzzsLXv/51jBgxok/yQURERLQtYJyBiIi2pC0ZZ7j22mvxk5/8pFdpdEZrvdnSJqJtDwdRIKKtQnt7O37+85/jlltuQWNjY7eW3bBhA5566ik89dRTuPjii3H66afjmmuuwU477bSZcktERIPFHXfcge9+97toa2vrct6FCxfivPPOw80334z7778fu+222xbIIRERERH1BOMMRETUX3K5HK6//nr88pe/REdHR8XLzJs3D/PmzcOrr76KF154YTPnkoiIiGjbwjgBERFtCY7j4C9/+QsWLFiA+fPnY+HChRXHBnrr8ccfx5133om///3vyOVyFS+ntca7776L//qv/8I111yDn/3sZ7jssssgpdyMuSUiIiLaujHOQEREW0J/xhmIiLYmjGQS0YD3/PPPY9ddd8VPf/rTbgcSiqXTadx3332YOnUqLr300pIRtYiIiHzf/e53ceGFF1Y0gELY22+/jQMPPBCvvfbaZsoZEREREfUG4wxERNRfVq1ahYMPPhjXXXcdOy8QERERDRCMExAR0ZbS1taGs846C7/5zW/wyiuvbNHYwK9//Ws8+eST3RpAoVhHRwcuv/xyHHfccWhvb+/D3BERERENHowzEBHRltKfcYbNacaMGf2dBSIaZOz+zgARUWduvPFG/PCHP4TWOvbvw4cPx+c+9zlMmzYNo0ePxujRo6G1RlNTE5YtW4b58+fj1VdfRTqdjiyXz+dx88034/LLL8fkyZO3wJYQEdHW5Be/+AV+9atflUyvra3Fl770Jey1114YNWoUVq5ciZdeeglPPfVU5F7V0tKCE088EQsWLMCUKVO2ZNaJiIiIqBOMMxARUX9ZtmwZDj/8cKxatarkb8OHD8fxxx+PvffeG2PGjEFdXR2am5uxevVqvPPOO3j55Zfx6aef9kOuiYiIiAY3xgmIiGhbN2zYMBx66KE44IADMGbMGIwaNQodHR1YtmwZZs+ejZdffrlkmX/84x845ZRT8PTTTyOVSvVDromIiIgGJsYZiIhoWzFu3DjsvffevU7n7bffLpl23nnn9TpdIqIwDqJARAPWVVddheuvvz72bzNnzsRVV12FGTNmwLKsTtPp6OjAE088gZtvvhmvvPLK5sgqERENIm+++SZ++MMflkz/whe+gHvuuQfDhw+PTP/e976H999/H6eeeio+/PDDYHpjYyO+/OUvY+7cuRBCbPZ8ExEREVHnGGcgIqL+0tjYiJkzZ5YMoDB27FjceOONOPPMM2Hb5R/Zaa3xxhtv4A9/+APWrVu3ubNLREREtE1gnICIiAaS8ePHY//994dSCk8//fRmXdfQoUPxxS9+Eeeccw4OOuggSClj5/vJT36Ct956C+eddx4WLFgQ+dvs2bNxww034Nprr92seSUiIiLaWjDOQEREA8nmjjNceOGFuPDCC3uVxmuvvYYZM2ZEptXV1eGLX/xir9IlIioWH/0kIupnd911V2wgYfz48Zg9ezaeffZZHHbYYV0GEgCgpqYGX/ziF/Hyyy/jH//4B/bYY4/NkWUiIhokvvWtb8FxnMi0M844Aw8//HDJAAq+adOmYe7cudh5550j01977TXce++9myurRERERFQhxhmIiKg/nXfeefjoo48i0w4++GAsWrQI55xzTqcDKACAEAIHHnggbrnlFjzwwAObM6tERERE2wTGCYiIqD+NHj0axx9/PK666io8+uijWLVqFdasWYMnnngCZ5xxxmZb7/jx4/Gb3/wGa9euxR133IEZM2aUHUDBt88++2Du3Lk4/vjjS/7285//vGTASCIiIqJtEeMMRETUn/orztBbd911V8m0//f//h9qa2v7ITdENJhxEAUiGnA++OADfOtb3yqZvttuu2Hu3Lk46qijepz20UcfjQULFuC73/1ub7JIRESD1OzZszF37tzItLFjx+L222/vsvPAyJEjcffdd5fM99///d9QSvV5XomIiIioMowzEBFRf3rkkUfw8MMPR6bts88+eO655zBs2LBupyeE6KOcEREREW2bGCcgIqL+Ul9fj08++QSffvopnnnmGfz0pz/FF77wBWy33Xabfd1XXnklli5diksvvRTV1dXdWjaZTOKBBx7A+PHjI9MzmQwHeyQiIqJtHuMMRETUX/ozztBb7e3tuP/++0umn3feef2QGyIa7DiIAhENOBdeeCEymUxk2ogRIzB79mxMnjy51+knk0n84he/wJ///Gckk8lep0dERIPHbbfdVjLte9/7HkaMGFHR8p/97Gdx4oknRqYtWbIEzz//fJ/kj4iIiIi6j3EGIiLqL0opfO9734tMs20bd999N4YMGdJPuSIiIiLatjFOQERE/cWyLEyaNKlf1n388cejqqqqx8vX1dXFvrz35JNP9iZbRERERFs9xhmIiKi/9Gecobfuv/9+tLW1RabtscceOOigg/opR0Q0mNn9nQEiorCnn34aL774Ysn03/72t5gwYUKfrutLX/pSj5ft6OjA66+/jjVr1qChoQGZTAajR4/Line truncated
|
||||
"text/plain": [
|
||||
"<Figure size 4200x2100 with 8 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from scipy import fft as scipy_fft\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"# Reconstruct every accepted orientation independently in every z layer.\n",
|
||||
"# scipy.fft keeps float32 input as complex64, limiting peak memory usage.\n",
|
||||
"orientation_amplitudes = np.empty(\n",
|
||||
" (len(orientation_fits), *coloring_volume.shape), dtype=np.float32\n",
|
||||
")\n",
|
||||
"for layer_index, layer in enumerate(coloring_volume):\n",
|
||||
" layer_fft = scipy_fft.fftshift(scipy_fft.fft2(layer))\n",
|
||||
" for orientation_index, mask in enumerate(orientation_masks):\n",
|
||||
" filtered = scipy_fft.ifft2(scipy_fft.ifftshift(layer_fft * mask))\n",
|
||||
" orientation_amplitudes[orientation_index, layer_index] = np.abs(filtered)\n",
|
||||
"\n",
|
||||
"# A global robust scale per orientation preserves brightness consistency in z.\n",
|
||||
"for orientation_index in range(len(orientation_fits)):\n",
|
||||
" amplitude = orientation_amplitudes[orientation_index]\n",
|
||||
" low, high = np.percentile(amplitude, (2, 99.7))\n",
|
||||
" if high <= low:\n",
|
||||
" orientation_amplitudes[orientation_index].fill(0)\n",
|
||||
" else:\n",
|
||||
" orientation_amplitudes[orientation_index] = np.clip(\n",
|
||||
" (amplitude - low) / (high - low), 0, 1\n",
|
||||
" )\n",
|
||||
"\n",
|
||||
"# The original signal provides continuous atom support and suppresses Fourier\n",
|
||||
"# ringing. Orientation contributions remain additive, so boundary atoms mix.\n",
|
||||
"atom_support = np.empty_like(coloring_volume)\n",
|
||||
"for layer_index, layer in enumerate(coloring_volume):\n",
|
||||
" low, high = np.percentile(layer, (5, 99.5))\n",
|
||||
" if high <= low:\n",
|
||||
" atom_support[layer_index].fill(0)\n",
|
||||
" else:\n",
|
||||
" atom_support[layer_index] = np.clip((layer - low) / (high - low), 0, 1) ** 1.5\n",
|
||||
"\n",
|
||||
"colored_grain_float = np.zeros((*coloring_volume.shape, 3), dtype=np.float32)\n",
|
||||
"for amplitude, color in zip(orientation_amplitudes, grain_colors):\n",
|
||||
" colored_grain_float += amplitude[..., None] * color\n",
|
||||
"colored_grain_float = np.clip(colored_grain_float, 0, 1)\n",
|
||||
"colored_grain_float *= atom_support[..., None]\n",
|
||||
"colored_grain_stack = np.round(255 * colored_grain_float).astype(np.uint8)\n",
|
||||
"\n",
|
||||
"colored_grain_path = os.path.abspath(\"colored_grains_by_layer.tiff\")\n",
|
||||
"tifffile.imwrite(\n",
|
||||
" colored_grain_path,\n",
|
||||
" colored_grain_stack,\n",
|
||||
" photometric=\"rgb\",\n",
|
||||
" metadata={\"axes\": \"ZYXS\"},\n",
|
||||
")\n",
|
||||
"print(f\"Saved {colored_grain_stack.shape} RGB stack to {colored_grain_path}\")\n",
|
||||
"\n",
|
||||
"# Preview representative layers and a maximum-color projection.\n",
|
||||
"preview_layers = np.unique(\n",
|
||||
" np.linspace(0, coloring_volume.shape[0] - 1, 6).round().astype(int)\n",
|
||||
")\n",
|
||||
"fig, axes = plt.subplots(2, 4, figsize=(14, 7), constrained_layout=True, dpi=300)\n",
|
||||
"for axis, layer_index in zip(axes.flat, preview_layers):\n",
|
||||
" axis.imshow(colored_grain_stack[layer_index])\n",
|
||||
" axis.set_title(f\"Colored layer z={layer_index}\")\n",
|
||||
" axis.set_axis_off()\n",
|
||||
"\n",
|
||||
"axes.flat[-2].imshow(colored_grain_stack.max(axis=0))\n",
|
||||
"axes.flat[-2].set_title(\"RGB maximum projection\")\n",
|
||||
"axes.flat[-2].set_axis_off()\n",
|
||||
"axes.flat[-1].imshow(\n",
|
||||
" fit_image,\n",
|
||||
" cmap=\"gray\",\n",
|
||||
" vmin=np.percentile(fit_image, 2),\n",
|
||||
" vmax=np.percentile(fit_image, 99.7),\n",
|
||||
")\n",
|
||||
"axes.flat[-1].set_title(\"Corrected summed projection\")\n",
|
||||
"axes.flat[-1].set_axis_off()\n",
|
||||
"plt.show()"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "lemon",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.12"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
@@ -0,0 +1,634 @@
|
||||
{
|
||||
"cells": [
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 1,
|
||||
"id": "a7e52d53",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": [
|
||||
"import os\n",
|
||||
"import numpy as np\n",
|
||||
"from matplotlib import pyplot as plt, colormaps\n",
|
||||
"import tifffile"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 2,
|
||||
"id": "74171f8c",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"['f_410_1_obj_phase_roi_Niter100.tiff',\n",
|
||||
" 'f_410_2_obj_phase_roi_Niter100.tiff',\n",
|
||||
" 'f_410_3_obj_phase_roi_Niter100.tiff',\n",
|
||||
" 'f_410_4_obj_phase_roi_Niter100.tiff',\n",
|
||||
" 'f_420_1_obj_phase_roi_Niter120.tiff',\n",
|
||||
" 'f_420_2_obj_phase_roi_Niter120.tiff',\n",
|
||||
" 'f_420_3_obj_phase_roi_Niter120.tiff',\n",
|
||||
" 'f_420_4_obj_phase_roi_Niter120.tiff',\n",
|
||||
" 'f_430_1_obj_phase_roi_Niter80.tiff',\n",
|
||||
" 'f_430_2_obj_phase_roi_Niter80.tiff',\n",
|
||||
" 'f_430_3_obj_phase_roi_Niter80.tiff',\n",
|
||||
" 'f_430_4_obj_phase_roi_Niter80.tiff',\n",
|
||||
" 'f_450_1_obj_phase_roi_Niter90.tiff',\n",
|
||||
" 'f_450_2_obj_phase_roi_Niter90.tiff',\n",
|
||||
" 'f_450_3_obj_phase_roi_Niter90.tiff',\n",
|
||||
" 'f_450_4_obj_phase_roi_Niter90.tiff',\n",
|
||||
" 'f_471_1_obj_phase_roi_Niter50.tiff',\n",
|
||||
" 'f_471_2_obj_phase_roi_Niter50.tiff',\n",
|
||||
" 'f_471_3_obj_phase_roi_Niter50.tiff',\n",
|
||||
" 'f_471_4_obj_phase_roi_Niter50.tiff',\n",
|
||||
" 'f_471_5_obj_phase_roi_Niter230.tiff',\n",
|
||||
" 'f_471_6_obj_phase_roi_Niter230.tiff',\n",
|
||||
" 'f_471_7_obj_phase_roi_Niter230.tiff',\n",
|
||||
" 'f_472_1_obj_phase_roi_Niter50.tiff',\n",
|
||||
" 'f_472_2_obj_phase_roi_Niter50.tiff',\n",
|
||||
" 'f_472_3_obj_phase_roi_Niter50.tiff',\n",
|
||||
" 'f_472_4_obj_phase_roi_Niter50.tiff']"
|
||||
]
|
||||
},
|
||||
"execution_count": 2,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"basedir = \"/home/swim/shared/Si_project/260707_BCAT_tif_file\"\n",
|
||||
"os.listdir(basedir)"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 5,
|
||||
"id": "80e55aa7",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Shape : (30, 379, 379)\n",
|
||||
"Dtype : uint8\n",
|
||||
"H × W : 30 × 379\n",
|
||||
"Pixel Size : 3.07 A/px\n"
|
||||
]
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"tiff = 'f_430_2_obj_phase_roi_Niter80.tiff'\n",
|
||||
"image = tifffile.imread(os.path.join(basedir, tiff))\n",
|
||||
"\n",
|
||||
"roi_A = 92\n",
|
||||
"A_per_px = roi_A / image.shape[0] # A/pixel\n",
|
||||
"Si_distance_px = 1.358 / A_per_px\n",
|
||||
"\n",
|
||||
"\n",
|
||||
"print(f\"Shape : {image.shape}\")\n",
|
||||
"print(f\"Dtype : {image.dtype}\")\n",
|
||||
"print(f\"H × W : {image.shape[0]} × {image.shape[1]}\")\n",
|
||||
"print(f\"Pixel Size : {A_per_px:.2f} A/px\")\n",
|
||||
"\n",
|
||||
"# fig, ax = plt.subplots(figsize=(7, 7))\n",
|
||||
"# ax.imshow(image[image.shape[0] // 2], cmap='gray')\n",
|
||||
"# ax.set_title(f\"Raw Image ({image.shape[0]} × {image.shape[1]} px)\")\n",
|
||||
"# ax.axis('on') # 픽셀 좌표 눈금 표시\n",
|
||||
"# plt.tight_layout()\n",
|
||||
"# plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 9,
|
||||
"id": "b6ac9727",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"(np.float64(-0.5), np.float64(378.5), np.float64(378.5), np.float64(-0.5))"
|
||||
]
|
||||
},
|
||||
"execution_count": 9,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 600x300 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"fig, axs = plt.subplots(1, 2, figsize=(6, 3))\n",
|
||||
"ax1, ax2 = axs\n",
|
||||
"\n",
|
||||
"layer = image[3]\n",
|
||||
"\n",
|
||||
"ax1.imshow(layer)\n",
|
||||
"ax1.axis('off')\n",
|
||||
"\n",
|
||||
"ax2.imshow(np.log1p(abs(np.fft.fftshift(np.fft.fft2(np.fft.ifftshift(layer))))))\n",
|
||||
"ax2.axis('off')"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 11,
|
||||
"id": "si110-grid-fit",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAABiUAAAGbCAYAAACrnJPxAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnXeYHVX9/1/Tbt9esimkQAi9Sgu9hRqKCIIiTaQIIigoRSBUhVgBQZAvGmmCgqCoSBPQH80gSu+QQEKy2exm660zc35/zH6Gc3c3ySZS9byeZ59kZ+fOnDlz5py570+zlFIKg8FgMBgMBoPBYDAYDAaDwWAwGAyGDxn7426AwWAwGAwGg8FgMBgMBoPBYDAYDIb/DYxRwmAwGAwGg8FgMBgMBoPBYDAYDAbDR4IxShgMBoPBYDAYDAaDwWAwGAwGg8Fg+EgwRgmDwWAwGAwGg8FgMBgMBoPBYDAYDB8JxihhMBgMBoPBYDAYDAaDwWAwGAwGg+EjwRglDAaDwWAwGAwGg8FgMBgMBoPBYDB8JBijhMFgMBgMBoPBYDAYDAaDwWAwGAyGjwRjlDAYDAaDwWAwGAwGg8FgMBgMBoPB8JFgjBIGg8FgMBgMBoPBYDAYDAaDwWAwGD4SjFHCYDAYDIZPGHPmzMGyrBF/zjjjDObNm4dlWcyZMyf+zOOPP84FF1xAd3f3sONdc801Vft+kFiWxQUXXLDCfUZq7/K44IILsCzrg2nccpD+nTdv3kr3feqpp/jsZz/LxIkTSSaTjBkzhunTp3P66adX7bfzzjuz8847r/R4+r38wQ9+UPW3c889l5kzZzJ+/Hgsy+Loo48e8RgvvvgiJ510EtOnTyebzWJZFo888shyz3nbbbex6aabkkqlGDduHKeddhr9/f1V+9x9991VbXv66adXei3/KZ+0cWEwGAwGg8FgMBgMho8GY5QwGAwGg+ETyi9/+UueeOKJqp+vf/3rjB07lieeeIJ999033vfxxx/nwgsv/MiNEqNhpPZ+GvjTn/7EtttuS29vL7Nnz+b+++/niiuuYLvttuP222+v2veaa67hmmuuGdVxjz32WJ544gkOP/zwqu0//vGP6ezsZP/99yeRSCz3808//TR33303jY2N7Lbbbis81y233MIXvvAFttxyS+69915mzZrFnDlzOOigg6r222mnnXjiiSc499xzR3UNHwSf1nFhMBgMBoPBYDAYDIb/DPfjboDBYDAYDIaR2XDDDdliiy1G/Ns222zzEbdm9Ukmk5+q9gqzZ89mypQp3Hfffbju+69Mhx12GLNnz67ad/311x/1cSdMmDBif/T19WHbkb/ITTfdtNzPH3HEERx11FEA3HHHHdxzzz0j7hcEAd/61rfYY489uP766wHYZZddqKmp4fDDD+fee+9l7733BqChoYFtttmGV155ZdTXsboEQYDv+5/acWEwGAwGg8FgMBgMhv8MEylhMBgMBsOnjKFpby644AK+9a1vATBlypQ4Bc8jjzzC5MmTefHFF3n00Ufj7ZMnT46P1dvbyxlnnMGUKVNIJBKMHz+e0047jYGBgapz9vb2ctxxx9HU1EQul2OvvfbitddeW632Cn/605/YdNNNSSaTTJkyZVg6I0EpxTXXXMOmm25KOp2moaGBgw8+mLfeeqtqvwceeIADDjiACRMmkEqlmDp1KieccAJLly4dVTuH0tnZSXNzc5VBQhDjgTDa9E0rYugx/9P9nnzySRYtWsQxxxxTtf2QQw4hl8tx1113rXIbda6//nqmTZtGMplk/fXX59Zbb+Xoo4+uGl9y72fPns0ll1zClClTSCaTPPzww//xuDAYDAaDwWAwGAwGw6cTEylhMBgMBsMnFPEo1xlJIP/KV75CV1cXV111Fb/73e8YO3YsEHnv33XXXRx88MHU1dXF6YWSySQA+XyenXbaiQULFnDOOeew8cYb8+KLL3L++efz/PPP8+CDD2JZFkopDjzwQB5//HHOP/98ttxySx577LHYy351eOihhzjggAOYPn06t912G0EQMHv2bNrb24fte8IJJzBnzhy+/vWvc/nll9PV1cVFF13Etttuy7PPPsuYMWMAePPNN5k+fTpf+cpXqKurY968efzoRz9i++235/nnn8fzvFVq4/Tp0/m///s/vv71r3P44Yez+eabr/IxPk5eeOEFADbeeOOq7Z7nse6668Z/Xx1+/vOfc8IJJ/C5z32OH//4x/T09HDhhRdSKpVG3P/KK69k2rRp/OAHP6C2tpa11157xP1WZVwYDAaDwWAwGAwGg+HTiTFKGAwGg8HwCWWk1DaVSmXYtgkTJjBx4kQANttssypP9c0224x0Ok1tbe2w41155ZU899xzPPXUU3GaqN12243x48dz8MEH85e//IW9996b++67j4cffpgrrriCr3/96wDMmDGDRCLBd77zndW6tu985zuMGTOGBx54gFQqBcCee+5Z1XaIvP2vv/56fvjDH/LNb34z3r7DDjswbdo0fvSjH3H55ZcDcOKJJ8Z/V0qx7bbbsvPOOzNp0iTuvfde9t9//1Vq42WXXcYrr7zCVVddxVVXXYXneWy55Zbst99+fO1rXyOXy63WtX9UdHZ2AtDY2Djsb42NjaMq9D0SYRgya9Ystt56a+644454+/bbb8/UqVMZN27csM+kUinuu+++KqPOSOcf7bgwGAwGg8FgMBgMBsOnF5O+yWAwGAyGTyg33ngjc+fOrfoZKVJidfnjH//IhhtuyKabborv+/HPnnvuGad/Anj44YcBhhVm/uIXv7ha5x0YGGDu3LkcdNBBsfAMUFNTw3777TesjZZl8aUvfamqjW1tbWyyySZxGwGWLFnCiSeeyBprrIHruniex6RJkwB4+eWXV7mdTU1N/P3vf2fu3LlcdtllHHDAAbz22mucffbZbLTRRqudFuqjxrKsVdq+Ml599VUWL17M5z//+artEydOZLvtthvxM/vvv/9Ko0xWZVwYDAaDwWAwGAwGg+HTi4mUMBgMBoPhE8p666233ELXHwTt7e288cYbyxWLRXTv7OzEdV2ampqq/t7W1rZa5122bBlhGI74+aHb2tvbUUrFKZqGsuaaawKR9/4ee+zBe++9x3nnncdGG21ENpslDEO22WYbCoXCarUVYIsttojvQ6VS4cwzz+THP/4xs2fPHlbw+pOE3K/Ozs5h/dfV1TViBMVokAiMke7JmDFjePvtt4dtl5RiK2JVxoXBYDAYDAaDwWAwGD69GKOEwWAwGAz/ozQ3N5NOp/nFL36x3L9DJG77vk9nZ2eVYWLx4sWrdd6GhgYsyxrx80O3NTc3Y1kWf//73+NaGDqy7YUXXuDZZ59lzpw5HHXUUfHf33jjjdVq4/LwPI9Zs2bx4x//+D+qyfBRsNFGGwHw/PPPs/7668fbfd/nlVde4Qtf+MJqHVfGwEh1HpY3JkYTlbEq48JgMBgMBoPBYDAYDJ9eTPomg8FgMBj+CxBxfqSIgGQyOeL2mTNn8uabb9LU1BRHA+g/ksd/l112AeCWW26p+vytt966Wm3NZrNstdVW/O53v6NYLMbb+/r6uOeee4a1USnFwoULR2yjCO8ieg81XFx33XWr1UaARYsWjbhdUkGNVDvhk8TWW2/N2LFjmTNnTtX2O+64g/7+fg466KDVOu4666xDW1sbv/nNb6q2v/POOzz++OOr29xVGhcGg8FgMBgMBoPBYPj0YiIlDAaDwWD4L0DE+SuuuIKjjjoKz/NYZ511qKmpYaONNuK2227j9ttvZ8011ySVSrHRRhtx2mmnceedd7LjjjvyjW98g4033pgwDHnnnXe4//77Of3009l6663ZY4892HHHHfn2t7/NwMAAW2yxBY899hg33XTTarf34osvZq+99mLGjBmcfvrpBEHA5ZdfTjabpaurK95vu+224/jjj+eYY47h6aefZscddySbzbJo0SL+3//7f2y00UZ89atfZd1112WttdbirLPOQilFY2Mj99xzDw888MBqt3HPPfdkwoQJ7Lfffqy77rqEYci///1vfvjDH5LL5Tj11FNX+9gj8eijj9LR0QFAEATMnz8/LiS900470dLSAkA+n+fPf/4zEBUCl88uXbqUbDbL3nvvDYDjOMyePZsjjjiCE044gS984Qu8/vrrfPvb32bGjBnstddeq9VO27a58MILOeGEEzj44IP58pe/THd3NxdeeCFjx47Ftlff52W048JgMBgMBoPBYDAYDJ9ejFHCYDAYDIb/AnbeeWfOPvtsfvWrX3H99dcThiEPP/wwO++8MxdeeCGLFi3iuOOOo6+vj0mTJjFv3jyy2Sx///vfueyyy/j5z3/O22+/TTqdZuLEiey+++5xpIRt2/zhD3/gm9/8JrNnz6ZcLrPddtvx5z//mXXXXXe12jtjxgzuvvtuzj33XA499FDa2to46aSTKBQKXHjhhVX7XnfddWyzzTZcd911XHPNNYRhyLhx49huu+3YaqutgCit0j333MOpp57KCSecgOu67L777jz44INMnDhxtdp47rnn8vvf/54f//jHLFq0iFKpxNixY9l99905++yzWW+99VbruMtj1qxZPProo/HvjzzySFWx8Z133hmICnofcsghVZ+94IILAOJ7K3zpS1/CcRwuu+wy5syZQ2NjI0ceeSSXXnrpf9TW448/HsuymD17Np/97GeZPHkyZ511Fr///e955513Vvu4qzIuDAaDwWAwGAwGg8Hw6cRSSqmPuxEGg8FgMBgM/wtYlsV5553H+eefj+M4o6q18FGglCIIAm688UaOPfZY5s6du8pF1ru7u5k2bRoHHnggP//5zz+klhoMBoPBYDAYDAaD4dOOqSlhMBgMBoPB8BFy8cUX43keP/zhDz/upsT8/ve/x/M8jj322FHtv3jxYk455RR+97vf8eijj3LjjTeyyy670NfX94GntTIYDAaLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1600x400 with 4 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Match the FFT to an ideal diamond-cubic Si [110] diffraction grid.\n",
|
||||
"# For zone axis [110], h + k = 0. The supplied fcc/diamond extinction\n",
|
||||
"# rules leave: (h, -h, l), with h and l both odd, or both even with l = 4n.\n",
|
||||
"layer = image[3].astype(float)\n",
|
||||
"height, width = layer.shape\n",
|
||||
"A_per_px_xy = roi_A / height\n",
|
||||
"d_004_A = 1.358 # given above; for Si, d_004 = a / 4\n",
|
||||
"a_si_A = 4 * d_004_A\n",
|
||||
"\n",
|
||||
"fft_shifted = np.fft.fftshift(np.fft.fft2(layer))\n",
|
||||
"spectrum = np.log1p(np.abs(fft_shifted))\n",
|
||||
"centre_y, centre_x = (np.asarray(layer.shape) - 1) / 2\n",
|
||||
"yy, xx = np.ogrid[:height, :width]\n",
|
||||
"radius_px = np.hypot(xx - centre_x, yy - centre_y)\n",
|
||||
"\n",
|
||||
"# Detect candidate Bragg maxima once. Keep only the strongest candidates so\n",
|
||||
"# the rotation/scale fit is driven by diffraction peaks rather than background.\n",
|
||||
"neighbourhood = 3\n",
|
||||
"local_maximum = spectrum.copy()\n",
|
||||
"for dy in range(-neighbourhood, neighbourhood + 1):\n",
|
||||
" for dx in range(-neighbourhood, neighbourhood + 1):\n",
|
||||
" local_maximum = np.maximum(local_maximum, np.roll(np.roll(spectrum, dy, 0), dx, 1))\n",
|
||||
"candidate_pixels = np.argwhere((spectrum == local_maximum) & (radius_px > 8))\n",
|
||||
"candidate_strength = spectrum[candidate_pixels[:, 0], candidate_pixels[:, 1]]\n",
|
||||
"strongest = np.argsort(candidate_strength)[-min(600, len(candidate_pixels)):]\n",
|
||||
"candidate_pixels = candidate_pixels[strongest]\n",
|
||||
"candidate_strength = candidate_strength[strongest]\n",
|
||||
"\n",
|
||||
"# Build all allowed reciprocal-lattice points in the [110] zone. Coordinates\n",
|
||||
"# are expressed along [1 -1 0] (horizontal) and [001] (vertical).\n",
|
||||
"grid_hkls, grid_xy = [], []\n",
|
||||
"base_scale_px = width * A_per_px_xy / a_si_A # pixels per reciprocal 1/a\n",
|
||||
"for h in range(-12, 13):\n",
|
||||
" for l in range(-12, 13):\n",
|
||||
" if h == 0 and l == 0:\n",
|
||||
" continue\n",
|
||||
" all_odd = (h % 2 != 0) and (l % 2 != 0)\n",
|
||||
" diamond_even = (h % 2 == 0) and (l % 2 == 0) and (l % 4 == 0)\n",
|
||||
" if all_odd or diamond_even:\n",
|
||||
" grid_hkls.append((h, -h, l))\n",
|
||||
" grid_xy.append((np.sqrt(2) * h * base_scale_px, l * base_scale_px))\n",
|
||||
"grid_hkls = np.asarray(grid_hkls)\n",
|
||||
"grid_xy = np.asarray(grid_xy)\n",
|
||||
"\n",
|
||||
"match_tolerance_px = 4.5\n",
|
||||
"def projected_grid(angle_deg, scale_factor):\n",
|
||||
" angle = np.deg2rad(angle_deg)\n",
|
||||
" rotation = np.array([[np.cos(angle), -np.sin(angle)],\n",
|
||||
" [np.sin(angle), np.cos(angle)]])\n",
|
||||
" xy = scale_factor * grid_xy @ rotation.T\n",
|
||||
" pixels = np.column_stack((centre_y + xy[:, 1], centre_x + xy[:, 0]))\n",
|
||||
" valid = ((pixels[:, 0] >= 4) & (pixels[:, 0] < height - 4)\n",
|
||||
" & (pixels[:, 1] >= 4) & (pixels[:, 1] < width - 4))\n",
|
||||
" return grid_hkls[valid], pixels[valid]\n",
|
||||
"\n",
|
||||
"def grid_score(angle_deg, scale_factor):\n",
|
||||
" _, targets = projected_grid(angle_deg, scale_factor)\n",
|
||||
" distance2 = ((targets[:, None, :] - candidate_pixels[None, :, :]) ** 2).sum(axis=2)\n",
|
||||
" nearest = np.argmin(distance2, axis=1)\n",
|
||||
" # Gaussian distance weighting rewards agreement with many allowed spots.\n",
|
||||
" return np.sum(candidate_strength[nearest] * np.exp(-distance2[np.arange(len(targets)), nearest]\n",
|
||||
" / (2 * match_tolerance_px ** 2)))\n",
|
||||
"\n",
|
||||
"# Fit detector rotation and a small scale correction to the measured FFT.\n",
|
||||
"best_score, best_angle, best_scale = -np.inf, None, None\n",
|
||||
"for scale in np.linspace(0.94, 1.06, 7):\n",
|
||||
" for angle in np.arange(0, 180, 2):\n",
|
||||
" score = grid_score(angle, scale)\n",
|
||||
" if score > best_score:\n",
|
||||
" best_score, best_angle, best_scale = score, angle, scale\n",
|
||||
"for scale in np.linspace(best_scale - 0.015, best_scale + 0.015, 7):\n",
|
||||
" for angle in np.linspace(best_angle - 2, best_angle + 2, 17):\n",
|
||||
" score = grid_score(angle, scale)\n",
|
||||
" if score > best_score:\n",
|
||||
" best_score, best_angle, best_scale = score, angle, scale\n",
|
||||
"\n",
|
||||
"# Retain only observed candidate peaks that are close to allowed, fitted-grid sites.\n",
|
||||
"fitted_hkls, fitted_pixels = projected_grid(best_angle, best_scale)\n",
|
||||
"distance2 = ((fitted_pixels[:, None, :] - candidate_pixels[None, :, :]) ** 2).sum(axis=2)\n",
|
||||
"nearest = np.argmin(distance2, axis=1)\n",
|
||||
"matched = []\n",
|
||||
"used_candidates = set()\n",
|
||||
"for hkl, distance, candidate_index in zip(fitted_hkls, distance2[np.arange(len(fitted_pixels)), nearest], nearest):\n",
|
||||
" if distance <= match_tolerance_px ** 2 and int(candidate_index) not in used_candidates:\n",
|
||||
" matched.append((hkl, candidate_pixels[candidate_index]))\n",
|
||||
" used_candidates.add(int(candidate_index))\n",
|
||||
"if not matched:\n",
|
||||
" raise RuntimeError('The fitted Si [110] grid did not match any FFT candidates.')\n",
|
||||
"\n",
|
||||
"mask_radius_px = 6\n",
|
||||
"si110_grid_mask = np.zeros(layer.shape, dtype=bool)\n",
|
||||
"for _, (y, x) in matched:\n",
|
||||
" si110_grid_mask |= (xx - x) ** 2 + (yy - y) ** 2 <= mask_radius_px ** 2\n",
|
||||
"si110_grid_fft = fft_shifted * si110_grid_mask\n",
|
||||
"si110_grid_image = np.fft.ifft2(np.fft.ifftshift(si110_grid_fft)).real\n",
|
||||
"\n",
|
||||
"fig, axes = plt.subplots(1, 4, figsize=(16, 4), constrained_layout=True)\n",
|
||||
"axes[0].imshow(layer, cmap='gray'); axes[0].set_title('Input layer')\n",
|
||||
"axes[1].imshow(spectrum, cmap='magma')\n",
|
||||
"axes[1].set_title(f'Fitted ideal Si [110] grid\\nrotation={best_angle:.2f}°, scale={best_scale:.3f}')\n",
|
||||
"axes[2].imshow(np.log1p(np.abs(si110_grid_fft)), cmap='magma', vmin=0, vmax=spectrum.max())\n",
|
||||
"axes[2].set_title(f'Matched allowed peaks ({len(matched)})')\n",
|
||||
"axes[3].imshow(si110_grid_image, cmap='gray'); axes[3].set_title('Grid-matched inverse FFT')\n",
|
||||
"for hkl, pixel in zip(fitted_hkls, fitted_pixels):\n",
|
||||
" axes[1].plot(pixel[1], pixel[0], 'w+', ms=4, mew=0.7, alpha=0.45)\n",
|
||||
"for hkl, (y, x) in matched:\n",
|
||||
" axes[1].plot(x, y, 'co', ms=6, mfc='none', mew=1.2)\n",
|
||||
"for ax in axes:\n",
|
||||
" ax.axis('off')\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 12,
|
||||
"id": "e928013c",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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mRVt2XfffStSVknKqNNPP32Dv/f4448DfkRFOZ/61KfYeuutefTRRyu2t7S08KlPfapi2/bbb8+KFSsqjrntttuyww47VOx39NFHb+TM/0F9fT1PPfUUS5cu5dJLL+WQQw7hzTff5Dvf+Q4zZsygp6dnTMf5uJFolbFufy/eeOMNOjs7+exnP1uxvb29nd1222293zn44IPfM8okk8mwdOlSDj/8cKLRaLA9lUpx0EEHva+2ajQajUaj0Wg0Go1m86CNEhqNRqPRbGZuuukmli5dymOPPcapp57Ka6+9xlFHHRV83tvbC/wjhVI5bW1teJ5Hf3//Rn9jeHiY3XffnSVLlnDxxRfzxBNPsHTpUu68804Acrnce7azq6sLpRTNzc3Ytl3x99xzzwVCubS3paVl1DHWt21d9thjD+666y4cx+H4449n/PjxbLfddtx6660VbXnppZdGtSOVSqGUCtrS3d2NZVkb/d336l/5XKivrx+1XyQSqejD3t7e933+5ey0005861vf4ne/+x1r1qzhrLPOYvny5aOKXf+zIX20bt8B9PX1UVdX976OK8eTNGHlrG8brP+6rkt/fz+e522Wa6bRaDQajUaj0Wg0ms2Lrimh0Wg0Gs1mZuuttw6KW++55564rssvf/lLbr/9do488shA4O3o6Bj13TVr1mCaJrW1tRv9jccee4w1a9bwxBNPBNERAAMDA2NuZ0NDA4Zh8NRTTxGJREZ9LtukvZ2dnaP26ezsrChGvCEOOeQQDjnkEAqFAs899xyXXHIJRx99NJMmTWL27Nk0NDQQi8W47rrrNthWgMbGRlzXpbOzc4PidHn/jh8/vuKzNWvWVNSTGCv19fUbPP/3i23bLFy4kCuvvPID1WT4KJgxYwYA//d//xfUNAFwHIfXX3+9wui2Kci1Wl+dhw317ViiMmprazEMY7NfM41Go9FoNBqNRqPRfHB0pIRGo9FoNB8yixYtora2lvPOOw/P85g2bRrjxo3jlltuQSkV7JfJZLjjjjuYPXs28Xgc+IdhYN3IBxFm1zUm/M///M+o39/QMRYsWIBSinfffZeddtpp1J8I0bvssgvRaJSbb7654vvPPvtsRYqjsRCJRJgzZw6XXXYZAH/961+Dtrz99tvU19evty1i+Nh///0B+NnPfrbB39hrr70A+PWvf12xfenSpbz22mvMmzdvk9oMvnHplVde4cUXX6zYfsstt4zp++szQME/0my1tbVtcps+SmbNmkVrays33HBDxfbbb7+d4eFhDj/88Pd13GnTptHS0sJvf/vbiu0rV67k2Weffb/NJZFI8KlPfYo777yTfD4fbE+n09xzzz3v+7gajUaj0Wg0Go1Go/ng6EgJjUaLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1600x400 with 4 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Second-dominant Si [110] grain. Run the preceding grid-fit cell first.\n",
|
||||
"# Keep the first grain's calibrated grid scale fixed; search only its rotation.\n",
|
||||
"# Remove every candidate close to the first orientation's full ideal grid.\n",
|
||||
"first_grid_pixels = fitted_pixels\n",
|
||||
"distance2_to_first = ((candidate_pixels[:, None, :] - first_grid_pixels[None, :, :]) ** 2).sum(axis=2)\n",
|
||||
"keep_residual = distance2_to_first.min(axis=1) > (2 * match_tolerance_px) ** 2\n",
|
||||
"second_candidate_pixels = candidate_pixels[keep_residual]\n",
|
||||
"second_candidate_strength = candidate_strength[keep_residual]\n",
|
||||
"if len(second_candidate_pixels) == 0:\n",
|
||||
" raise RuntimeError('No FFT candidates remain after excluding the first grid.')\n",
|
||||
"\n",
|
||||
"second_scale = best_scale\n",
|
||||
"def second_grid_score(angle_deg):\n",
|
||||
" _, targets = projected_grid(angle_deg, second_scale)\n",
|
||||
" distance2 = ((targets[:, None, :] - second_candidate_pixels[None, :, :]) ** 2).sum(axis=2)\n",
|
||||
" nearest = np.argmin(distance2, axis=1)\n",
|
||||
" nearest_distance2 = distance2[np.arange(len(targets)), nearest]\n",
|
||||
" matches = nearest_distance2 <= match_tolerance_px ** 2\n",
|
||||
" return np.sum(second_candidate_strength[nearest[matches]]\n",
|
||||
" * (1 - nearest_distance2[matches] / match_tolerance_px ** 2))\n",
|
||||
"\n",
|
||||
"# Coarse-to-fine rotation-only fit of the strongest remaining Si [110] grid.\n",
|
||||
"second_score, second_angle = -np.inf, None\n",
|
||||
"for angle in np.arange(0, 180, 1):\n",
|
||||
" score = second_grid_score(angle)\n",
|
||||
" if score > second_score:\n",
|
||||
" second_score, second_angle = score, angle\n",
|
||||
"for angle in np.linspace(second_angle - 1, second_angle + 1, 41):\n",
|
||||
" score = second_grid_score(angle)\n",
|
||||
" if score > second_score:\n",
|
||||
" second_score, second_angle = score, angle\n",
|
||||
"\n",
|
||||
"second_hkls, second_grid_pixels = projected_grid(second_angle, second_scale)\n",
|
||||
"relative_rotation = (second_angle - best_angle + 90) % 180 - 90\n",
|
||||
"distance2 = ((second_grid_pixels[:, None, :] - second_candidate_pixels[None, :, :]) ** 2).sum(axis=2)\n",
|
||||
"nearest = np.argmin(distance2, axis=1)\n",
|
||||
"second_matched, used_candidates = [], set()\n",
|
||||
"for hkl, distance, candidate_index in zip(second_hkls, distance2[np.arange(len(second_grid_pixels)), nearest], nearest):\n",
|
||||
" if distance <= match_tolerance_px ** 2 and int(candidate_index) not in used_candidates:\n",
|
||||
" second_matched.append((hkl, second_candidate_pixels[candidate_index]))\n",
|
||||
" used_candidates.add(int(candidate_index))\n",
|
||||
"if not second_matched:\n",
|
||||
" raise RuntimeError('No second Si [110] grid peaks matched the residual FFT.')\n",
|
||||
"\n",
|
||||
"# Pass every allowed peak in the fitted second-grain grid, including weak sites\n",
|
||||
"# that were not strong enough to be used as orientation-matching candidates.\n",
|
||||
"second_mask = np.zeros(layer.shape, dtype=bool)\n",
|
||||
"for y, x in second_grid_pixels:\n",
|
||||
" second_mask |= (xx - x) ** 2 + (yy - y) ** 2 <= mask_radius_px ** 2\n",
|
||||
"second_fft = fft_shifted * second_mask\n",
|
||||
"second_grain_image = np.fft.ifft2(np.fft.ifftshift(second_fft)).real\n",
|
||||
"\n",
|
||||
"fig, axes = plt.subplots(1, 4, figsize=(16, 4), constrained_layout=True)\n",
|
||||
"axes[0].imshow(layer, cmap='gray'); axes[0].set_title('Input layer')\n",
|
||||
"axes[1].imshow(spectrum, cmap='magma')\n",
|
||||
"axes[1].set_title(f'Rotated second Si [110] grid\\nΔrotation={relative_rotation:.2f}°')\n",
|
||||
"axes[2].imshow(np.log1p(np.abs(second_fft)), cmap='magma', vmin=0, vmax=spectrum.max())\n",
|
||||
"axes[2].set_title(f'All allowed second-grid peaks ({len(second_grid_pixels)})')\n",
|
||||
"axes[3].imshow(second_grain_image, cmap='gray'); axes[3].set_title('Second-grain inverse FFT')\n",
|
||||
"for pixel in first_grid_pixels:\n",
|
||||
" axes[1].plot(pixel[1], pixel[0], 'w+', ms=3, mew=0.5, alpha=0.18)\n",
|
||||
"for pixel in second_grid_pixels:\n",
|
||||
" axes[1].plot(pixel[1], pixel[0], 'y+', ms=4, mew=0.8, alpha=0.5)\n",
|
||||
"for _, (y, x) in second_matched:\n",
|
||||
" axes[1].plot(x, y, 'co', ms=6, mfc='none', mew=1.2)\n",
|
||||
"for ax in axes:\n",
|
||||
" ax.axis('off')\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 22,
|
||||
"id": "52b69c9b",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAABxMAAAHhCAYAAAClRpDpAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnWeYU9XWgN+UyUymN5hKHWlSRToiTaSLerFhAbtYUK9YsFHkimLv5do+L6hYsAAiFhBRuvQqbYABZmB6n0nZ34+dk0yYyUmmJaDnfZ48MMlOzjrn7LPXXmvttbZOCCHQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQOA19oAXQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NA4M9GCiRoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGjWiBRM1NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDRqRAsmamhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGho1IgWTNTQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0KgRLZiooaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaFRI1owUUNDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDo0a0YKKGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhoaGhkaNaMFEDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ2NGtGCiX9D1q5dyxVXXEFSUhImk4nExETGjx/PmjVravU7M2bMQKfT1UmGX3/9FZ1Ox6+//lqn7/vKoEGDGDRokE9tW7ZsyaRJkxpVHo26c/z4cWbMmMGWLVsCLUqtSU9PR6fT8fzzzwdaFA0NjQbio48+QqfT1fiaOnWq87n/6KOPGuyYb775ZoP+ni/4QzeuXr2aGTNmkJ+fX+ff+P7775kxY0aNn/3d9LvS9zZu3Oi17aRJk2jZsmXjC6WhofG3ZN26dVx22WU0b96c4OBgEhIS6Nu3Lw888ECgRWsQ/KkfdDqdRz3VUKjpQl/55JNPePnll2v8zB/n8HehNn4IDY2zgaq2T01+PCEE55xzDjqdrtH6vr/8Ktrz6zun69HG9tsdOHCA4ODgWvuv/U19/OUa/qe+fhaLxUJaWprH+dM/DS2Y+Dfjtddeo3///mRkZDB37lx+/vlnnn/+eY4dO8YFF1zA66+/7vNv3XLLLXUewLt3786aNWvo3r17nb6v8c/j+PHjzJw586wMJmpoaPx9+fDDD1mzZo3ba8qUKSQlJbFmzRpGjx7dYMcKRDDx66+/5oknnmjUY6xevZqZM2fWO5g4c+bMGj/zxzloaGho/N1YsmQJ/fr1o7CwkLlz5/Ljjz/yyiuv0L9/fxYsWBBo8c461qxZwy233NKox1DThb6iFkz0xzloaGic2URERPD+++9Xe3/lypUcOHCAiIiIAEilEShOt7Ma2283depUhg0bRt++fRvl9zX+mdTXzxIUFMSTTz7JrFmzyMnJaTjBzlKMgRZAo+H4448/uO+++xg1ahRff/01RqPr9l599dVcdtll3HvvvZx33nn079/f4++UlpYSGhpKamoqqampdZIlMjKSPn361Om7/1TKysowm82BFsPv2Gw2rFZroMXQ0NDQqJFOnTrRo0ePGj/zRc8pOvVMQ9E55513XqBFqTd/h3PQ0NDQ8Ddz586lVatWLFu2rJrdOHfu3ABKdvYghKC8vByz2fy3sH3/DufgjTN1XqahcaZw1VVXMX/+fN544w0iIyOd77///vv07duXwsLCAEqn4W/8aWft3r2bb775hh9++MFvx6wtmg7553LNNdfw73//m3feeYdHH3000OIEFC0z8W/EnDlz0Ol0vPXWW24GIYDRaOTNN99Ep9PxzDPPON9XUrM3bdrE+PHjiYmJIS0tze2zqlRUVPDAAw+QmJhIaGgoF154IX/++We11PeaypxOmjSJ8PBw9u/fz6hRowgPD6dZs2Y88MADVFRUuB1n5syZ9O7dm9jYWCIjI+nevTvvv/8+QogGulpQXl7OAw88QLdu3YiKiiI2Npa+ffvy7bffurUbOnQo7du3r3ZspcxD1ayUyspKZs+eTfv27QkODqZJkybceOONnDp1yu27LVu2ZMyYMSxcuJDzzjuPkJAQ1VWmmzdvZsyYMTRt2pTg4GCSk5MZPXo0GRkZAKrl9k4vV6Pc182bN3P55ZcTGRlJVFQU1113nUc5v/76a7p06UJISAitW7fm1VdfrXacI0eOcN111zll7NChAy+88AJ2u93ZRpFz7ty5zJ49m1atWhEcHMyKFSvo2bMnADfeeKOzvIZamR2lDMfy5cu59dZbiYuLIzIykhtuuIGSkhIyMzO58soriY6OJikpialTp2KxWNx+w9d+tnz5cgYNGkRcXBxms5nmzZvzr3/9i9LSUo/yWSwWJk6cSHh4OIsXL/bYTkND4+ykpnFXTacePHiQq6++muTkZGcpuaFDhzpXdbZs2ZKdO3eycuVK5xjorXxleXk506ZNo1WrVphMJlJSUrjrrruqZQCq6ZyaSsAVFhYydepUt9+97777KCkpcWun0+m4++67+d///keHDh0IDQ2la9eubmPejBkzePDBBwFo1apVtfJJCxYs4OKLLyYpKQmz2UyHDh145JFH3I41adIk3njjDecxlVd6errHc6iNTnr++ed58cUXadWqFeHh4fTt25e1a9eqXnt/kJeXx4033khsbCxhYWGMHTuWgwcPqn6nNvMBgH379jFhwgS366Rcaw0Njb83OTk5xMfHV7MbAfT66m6CBQsW0LdvX8LCwggPD2f48OFs3ry5Wrt169YxduxY4uLiCAkJIS0tjfvuu8+tze+//87QoUOJiIggNDSUfv36sWTJErc2ylx/xYoVTJ48mfj4eOLi4rj88ss5fvy4W1uLxcJDDz3ktFEvuOAC1q9f7/O1yM3N5c477yQlJQWTyUTr1q157LHHqtmoit57++236dChA8HBwfzf//2f87PTx9jMzExuv/12UlNTMZlMtGrVipkzZ7otpPRVF3nThW+88QYXXnghTZs2JSwsjM6dOzN37lw3+2fQoEEsWbKEw4cPu/1G1fM7/Rx27NjBuHHjiImJISQkhG7dujnPWUGx/T/99FMee+wxkpOTiYyM5KKLLmLv3r0+34eGRm1eJoTgzTffpFu3bpjNZmJiYhg/fnw1PSuEYO7cubRo0YKQkBC6d+/O0qVLA3E6Ghp+4ZprrgHg008/db5XUFDAV199xU033VTjd/ztV/HX82u323nttdecx4mOjqZPnz589913bm3mzp3r9AE2bdqUG264wemrUxg0aBCdOnVizZo19OvXD7PZTMuWLfnwww8BWS2ge/fuhIaG0rlz52oBtdr48XyVyZufEdztrF9//dWr327jxo1ccsklxMbGEhISwnnnncfnn3/u0/V+6623SExMZNiwYdU+++CDD+jatSshISHExsZy2WWXsXv3bufnL7/8Mjqdjv3791f77sMPP4zJZCI7O9v53s8//8zQoUOJjIwkNDSU/v3788svv7h9T02H1IQvdi24fOM7d+5k6NChhIWF0aRJE+6++27VZ6ExUGTZs2cPw4cPJywsjKSkJGfcYO3atVxwwQWEhYXRtm3bavr/1KlT3HnnnZx77rmEh4fTtGlThgwZwqpVq6odKyMjg/HjxxMREUF0dDTXXnstGzZsqNF29aUf+TpPVPOz+BoXADCZTFx11VW8++67DRqbOBvRMhP/JthsNlasWEGPHj08ZhM2a9aM888/n+XLl2Oz2TAYDM7PLr/8cq6++mruuOOOagNdVW688UYWLFjAQw89xJAhQ9i1axeXXXaZz6uTLBYLl1xyCTfffDMPPPAAv/32G0899RRRUVE8+eSTznbp6encfvvtNG/eHJAD2D333MOxY8fc2tWHiooKcnNzmTp1KikpKVRWVvLzzz9z+eWX8+GHH3LDDTcAcO+99zJu3Dh++eUXLrroIuf3ly5dyoEDB5yBNbvdzrhx41i1ahUPPfQQ/fr14/Dhw0yfPp1BgwaxceNGt8zDTZs2sXv3bh5//HFatWpFWFhYjXKWlJQwbNgwWrVqxRtvvEFCQgKZmZmsWLGCoqKiOp//ZZddxpVXXskdd9zBzp07eeKJJ9i1axfr1q0jKCjI2W7Lli3cd999zJgxg8TERObPn8+9995LZWUlU6dOBaQC6devH5WVlTz11FO0bNmSxYsXM3XqVA4cOMCbb77pduxXX32Vtm3b8vzzzxMZGUlCQgIffvghN954I48//rgzQOtLZuwtt9zC5ZdfzmeffcbmzZt59NFHsVqt7N27l8svv5zbbruNn3/+mWeffZbk5GT+/e9/O7/rSz9LT09n9OjRDBgwgA8++IDo6GiOHTvGDz/8QGVlZY2rkvLz87n88svZvXs3K1eu5Pzzz6/l3dHQ0DhTqCl7uibHa1Vq0qmjRo3CZrMxd+5cmjdvTnZ2NqtXr3YG/r7++mvGjx9PVFSUc8wMDg72eAwhBJdeeim//PIL06ZNY8CAAWzbto3p06c7y7FW/b6vOqe0tJSBAweSkZHBo48+SpcuXdi5cydPPvkk27dv5+eff3ZzPC5ZsoQNGzYwa9YswsPDmTt3Lpdddhl79+6ldevW3HLLLeTm5vLaa6+xcOFCkpKSADj33HMBGcwaNWoU9913H2FhYezZs4dnn32W9evXs3z5cgCeeOIJSkpK+PLLL93Kryu/dTq11UlvvPEG7du3d5Z9e+KJJxg1ahSHDh0iKirK4z1obG6++WaGDRvGJ598wtGjR3n88ccZNGgQ27ZtIzo6ut6/v2vXLvr160fz5s154YUXSExMZNmyZUyZMoXs7GymT59e/5PQ0NA4Y+nbty/vvfceU6ZM4dprr6V79+5uNkBVnn76aR5//HHnXL2yspLnnnuOAQMGsH79eueYvmzLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1800x500 with 4 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Composite of the two fitted Si [110] grain reconstructions.\n",
|
||||
"# Run the first-grid and second-grid cells before this cell.\n",
|
||||
"def display_strength(filtered_image):\n",
|
||||
" amplitude = np.abs(filtered_image)\n",
|
||||
" low, high = np.percentile(amplitude, (1, 99.5))\n",
|
||||
" if high == low:\n",
|
||||
" return np.zeros_like(amplitude)\n",
|
||||
" return np.clip((amplitude - low) / (high - low), 0, 1)\n",
|
||||
"\n",
|
||||
"first_blue = display_strength(si110_grid_image)\n",
|
||||
"second_red = display_strength(second_grain_image)\n",
|
||||
"\n",
|
||||
"# Use the original layer as a real-space support mask. Pixels below this\n",
|
||||
"# contrast level cannot receive Fourier-filtered color.\n",
|
||||
"mask_low, mask_high = np.percentile(layer, (5, 95))\n",
|
||||
"support_threshold = 0.5 # increase to reject more low-signal pixels\n",
|
||||
"if mask_high == mask_low:\n",
|
||||
" original_mask = np.ones(layer.shape, dtype=bool)\n",
|
||||
"else:\n",
|
||||
" original_support = np.clip((layer - mask_low) / (mask_high - mask_low), 0, 1)\n",
|
||||
" original_mask = original_support >= support_threshold\n",
|
||||
"\n",
|
||||
"composite = np.zeros((*layer.shape, 3))\n",
|
||||
"composite[..., 0] = second_red # second orientation: red\n",
|
||||
"composite[..., 1] = first_blue # first orientation: blue\n",
|
||||
"composite *= original_mask[..., None]\n",
|
||||
"\n",
|
||||
"first_rgb = np.zeros_like(composite)\n",
|
||||
"first_rgb[..., 1] = first_blue\n",
|
||||
"first_rgb *= original_mask[..., None]\n",
|
||||
"second_rgb = np.zeros_like(composite)\n",
|
||||
"second_rgb[..., 0] = second_red\n",
|
||||
"second_rgb *= original_mask[..., None]\n",
|
||||
"\n",
|
||||
"fig, axes = plt.subplots(1, 4, figsize=(18, 5), constrained_layout=True)\n",
|
||||
"axes[0].imshow(original_mask, cmap='gray'); axes[0].set_title('Original-layer support mask')\n",
|
||||
"axes[1].imshow(first_rgb); axes[1].set_title('First orientation — blue')\n",
|
||||
"axes[2].imshow(second_rgb); axes[2].set_title('Second orientation — red')\n",
|
||||
"axes[3].imshow(composite); axes[3].set_title('Masked composite (overlap = magenta)')\n",
|
||||
"for ax in axes:\n",
|
||||
" ax.axis('off')\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": null,
|
||||
"id": "216b020a",
|
||||
"metadata": {},
|
||||
"outputs": [],
|
||||
"source": []
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 24,
|
||||
"id": "25609e99",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Saved (32, 379, 379, 3) RGB stack to /home/swim/sandbox/silicon-grain-boundaries/colored.tiff\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAeEAAAH2CAYAAABHmTQtAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjYsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvq6yFwwAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzsnXecXNV5v587vW3vXaveGyDRhRC9d2Njg+3EODiOSeIkduK4l7jgOAlOwHZibAPGmN6RBEJCQkK9d2l7b9P7zD2/P85ISLBzZ3ZnNSP7N8/nMzY7Onfn7Mzc857zlu+rCCEEefLkyZMnT56so8v1BPLkyZMnT57/X8kb4Tx58uTJkydH5I1wnjx58uTJkyPyRjhPnjx58uTJEXkjnCdPnjx58uSIvBHOkydPnjx5ckTeCOfJkydPnjw5Im+E8+TJkydPnhyRN8J58uTJkydPjsgb4TwfYc+ePXzmM5+hubkZi8WCw+Fg8eLF/PjHP2ZkZGTMv+/Tn/40kyZNmviJZkBbWxuKovCb3/wmK6/38MMPM3XqVEwmE4qi4HK5svK6ZyPf+ta3UBQl19MYN//6r//KDTfcQF1dHYqi8OlPfzrpWCEEjz32GEuWLMFut1NYWMjixYt56aWXsjfhPGc1eSOc5zR+9atfcc4557B161b+8R//kTfffJMXXniBO++8k0cffZS/+Iu/yPUU/+TYtWsXX/rSl1i+fDlr1qxh06ZNFBQU5HpaecbJz372M4aHh7npppswmUyaYx944AEeeOABVqxYwcsvv8wzzzzDJz7xCQKBQJZmm+dsx5DrCeQ5e9i0aRMPPPAAV155JS+++CJms/nkv1155ZV8+ctf5s0338zhDCXBYBCr1ZrT17dYLGmf5vbv3w/A5z73OZYsWTIhcwgEAthstgn5XXlOJ9V76/V60enk+eXxxx9POu7FF1/kF7/4BU8//TR33XXXyeevvvrqiZtsnj958ifhPCf5wQ9+gKIo/PKXvzzNAJ/AZDJx0003nfxZVVV+/OMfM3PmTMxmM5WVldx77710dXWlfK1QKMQ///M/09zcjMlkoq6ujr/+67/+iJt20qRJ3HDDDTz//PMsWrQIi8XCt7/9bQD6+vr4/Oc/T319PSaTiebmZr797W8Ti8VO+x09PT3cddddFBQUUFRUxMc+9jH6+vrSek9+85vfoCgKq1at4rOf/SwVFRXYbDbC4TAATz/9NBdccAF2ux2Hw8HVV1/Nzp07T15/2WWX8clPfhKApUuXfsR9+dZbb7FixQoKCwux2WxcdNFFvP3226fN4YT7dseOHdxxxx2UlJQwZcoUQLo7/+d//oeFCxditVopKSnhjjvuoKWl5bTfcdlllzF37ly2bt3KJZdcgs1mY/Lkyfzwhz9EVdXTxrpcLr785S8zefLkk5/rddddx6FDh06OiUQifO973zv52VdUVPCZz3yGwcHBtN7XD/P0009z1VVXUVNTg9VqZdasWXz1q1/F7/efHPP444+jKAqbNm36yPXf+c53MBqN9PT0TNh7m4wTBjgV//mf/8mkSZNOM8B58nwEkSePECIWiwmbzSaWLl2a9jX333+/AMQXv/hF8eabb4pHH31UVFRUiIaGBjE4OHhy3H333SeamppO/qyqqrj66quFwWAQX//618WqVavEQw89JOx2u1i0aJEIhUInxzY1NYmamhoxefJk8etf/1q88847YsuWLaK3t1c0NDSIpqYm8Ytf/EK89dZb4rvf/a4wm83i05/+9MnrA4GAmDVrligqKhIPP/ywWLlypfjSl74kGhsbBSAee+wxzb/xscceE4Coq6sT999/v3jjjTfEs88+K2KxmPj+978vFEURn/3sZ8Wrr74qnn/+eXHBBRcIu90u9u/fL4QQYv/+/eJf//VfT77Wpk2bxLFjx4QQQjz++ONCURRxyy23iOeff1688sor4oYbbhB6vV689dZbJ+fwzW9+UwCiqalJfOUrXxGrV68WL774ohBCiM997nPCaDSKL3/5y+LNN98Uv//978XMmTNFVVWV6OvrO/k7li1bJsrKysS0adPEo48+KlavXi2+8IUvCED89re/PTnO4/GIOXPmCLvdLr7zne+IlStXiueee048+OCDYs2aNUIIIeLxuLjmmmuE3W4X3/72t8Xq1avF//7v/4q6ujoxe/ZsEQgENN/TE3/PqXz3u98VP/vZz8Rrr70m1q5dKx599FHR3Nwsli9ffnJMOBwW1dXV4p577jnt2mg0Kmpra8Wdd9558rmJeG/TwW63i/vuu+8jz0ejUWE2m8Wtt94qfvrTn4rGxkah0+lEc3Oz+MlPfiJUVU37NfL8eZM3wnmEEEL09fUJQNx9991pjT948KAAxBe+8IXTnt+8ebMAxL/8y7+cfO7DRvjNN98UgPjxj3982rVPP/20AMQvf/nLk881NTUJvV4vDh8+fNrYz3/+88LhcIj29vbTnn/ooYcEcNIIPvLIIwIQL7300mnjPve5z43JCN97772nPd/R0SEMBoP4m7/5m9Oe93q9orq6Wtx1110f+R1bt249+Zzf7xelpaXixhtvPO36eDwuFixYIJYsWXLyuROG4hvf+MZpYzdt2iQA8dOf/vS05zs7O4XVahX/9E//dPK5ZcuWCUBs3rz5tLGzZ88WV1999cmfv/Od7whArF69Oul78tRTTwlAPPfcc6c9v3XrVgGI//mf/0l67al/TzJUVRXRaFSsW7dOAGL37t2nXWsymUR/f//J5058b9atWyeEmJj3Nl2SGeHe3l4BiMLCQlFfXy9++9vfirffflv81V/91Ufujzz/f5N3R+cZF++88w7ARzJDlyxZwqxZsz7i9juVNWvWjHrtnXfeid1u/8i18+fPZ/r06ac99+qrr7J8+XJqa2uJxWInH9deey0A69atOznPgoKC09zoAJ/4xCfS+0MT3H777af9vHLlSmKxGPfee+9pr2+xWFi2bBlr167V/H0bN25kZGSE++6777TrVVXlmmuuYevWrae5Ykebw6uvvoqiKHzyk5887XdUV1ezYMGCj8yhurr6IzHp+fPn097efvLnN954g+nTp3PFFVcknfurr75KcXExN95442mvu3DhQqqrq1P+7aPR0tLCJz7xCaqrq9Hr9RiNRpYtWwbAwYMHT4574IEHAJlAeIKf//znzJs3j0svvRSYmPc2U064+D0eD8888wz33nsvl19+OY888gi33HIL//7v/47P55vQ18zzp0k+MSsPAOXl5dhsNlpbW9MaPzw8DEBNTc1H/q22tva0hX20aw0GAxUVFac9rygK1dXVJ3/3CUZ7jf7+fl555RWMRuOorzE0NHTytaqqqj7y79XV1UnnNxofnkN/fz8A55133qjjU8UNT1x/xx13JB0zMjKC3W7XnIMQYtS/D2Dy5Mmn/VxWVvaRMWazmWAwePLnwcFBGhsbU87d5XIlzQw+8d6ni8/n45JLLsFisfC9732P6dOnY7PZ6Ozs5LbbbjttflVVVXzsYx/jF7/4BV/96lfZv38/69ev5xe/+MVp84PM3ttMKSkpQVEUCgoKOP/880/7t2uvvZYXX3yRAwcOTFiiXp4/XfJGOA8Aer2eFStW8MYbb9DV1UV9fb3m+BMLem9v70fG9vT0UF5ernltLBZjcHDwNEMshKCvr+8jhm20LOTy8nLmz5/P97///VFfo7a29uRrbdmy5SP/nm5iVrI5nPj7nn32WZqamsb0u069/uGHH/7IIn2CDxvX0eagKArr168fNZFutOdSUVFRkTKxrry8nLKysqSZ8mMtv1qzZg09PT2sXbv25OkXSFpL/eCDD/L444/z0ksv8eabb1JcXMw999xz2vwgs/c2U6xWK9OmTRv1eyaEANJP8Mrz503eCOc5yT//8z/z+uuv87nPfY6XXnrpIyedaDTKm2++yY033sjll18OwBNPPHGa0dy6dSsHDx7ka1/7WtLXWbFiBT/+8Y954okn+Lu/+7uTzz/33HP4/X5WrFiRcq433HADr7/+OlOmTKGkpCTpuOXLl/PHP/6Rl19++TSX9O9///uUr6HF1VdfjcFg4Pjx4+NyZV500UUUFxdz4MABvvjFL45rDjfccAM//OEP6e7unrAM3GuvvZZvfOMbrFmz5uRnPNrr/uEPfyAej7N06dKMX/OEAfzwpuHU0+2pnHPOOVx44YX86Ec/Yt++fdx///2nnWon4r2dCG6//Xb+7d/+jY0bN3LhhReefP7111/H4XAwZ86cnM0tz9lD3gjnOckFF1zAI488whe+8AXOOeccHnjgAebMmUM0GmXnzp388pe/ZO7cudx4443MmDGD+++/n4cffhidTse1115LW1sbX//612loaDjNuH6YK6+8kquvvpqvfOUreDweLrroIvbs2cM3v/lNFi1axKc+9amUc/3Od77D6tWrufDCC/nSl77EjBkzCIVCtLW18frrr/Poo49SX1/Pvffey89+9jPuvfdevv/97zNt2jRef/11Vq5cmdF7NWnSJL7zne/wta99jZaWFq655hpKSkro7+9ny5Yt2O32k6VUo+FwOHj44Ye57777GBkZ4Y477qCyspLBwUF2797N4OAgjzzyiOYcLrroIu6//34+85nPsG3bNi699FLsdju9vb1s2LCBefPmnYyhpsvf/u3f8vTTT3PzzTfz1a9+lSVLlhAMBlm3bh033HADy5cv5+677+bJJ5/kuuuu48EHH2TJkiUYjUa6urp45513uPnmm7n11lvTfs0LL7yQkpIS/uqv/opvfvObGI1GnnzySXbv3p30mgcffJCPfexjKIrCF77whdP+bSLeWy3WrVt3shQrHo/T3t7Os88+C8CyZctOenf+4R/+gSeffJI777yT7373u9TX1/Pss8/y8ssv89BDD+W01j3PWUSOE8PynIXs2rVL3HfffaKxsVGYTKaTpUPf+MY3xMDAwMlLine truncated
|
||||
"text/plain": [
|
||||
"<Figure size 600x600 with 1 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# Color the complete TIFF stack using the two orientations fitted on the reference layer.\n",
|
||||
"# Run the first-grid and second-grid cells before this cell.\n",
|
||||
"volume = image.astype(float)\n",
|
||||
"\n",
|
||||
"# Fixed reciprocal-space masks from the reference layer are applied to every TIFF layer.\n",
|
||||
"fft_volume = np.fft.fftshift(np.fft.fft2(volume, axes=(-2, -1)), axes=(-2, -1))\n",
|
||||
"first_volume = np.fft.ifft2(\n",
|
||||
" np.fft.ifftshift(fft_volume * si110_grid_mask[None, :, :], axes=(-2, -1)), axes=(-2, -1)\n",
|
||||
").real\n",
|
||||
"second_volume = np.fft.ifft2(\n",
|
||||
" np.fft.ifftshift(fft_volume * second_mask[None, :, :], axes=(-2, -1)), axes=(-2, -1)\n",
|
||||
").real\n",
|
||||
"\n",
|
||||
"def normalize_volume(filtered_volume):\n",
|
||||
" amplitude = np.abs(filtered_volume)\n",
|
||||
" low, high = np.percentile(amplitude, (1, 99.5))\n",
|
||||
" if high == low:\n",
|
||||
" return np.zeros_like(amplitude)\n",
|
||||
" return np.clip((amplitude - low) / (high - low), 0, 1)\n",
|
||||
"\n",
|
||||
"first_blue_volume = normalize_volume(first_volume)\n",
|
||||
"second_red_volume = normalize_volume(second_volume)\n",
|
||||
"\n",
|
||||
"# Build an original-image support mask per layer to suppress Fourier ringing.\n",
|
||||
"stack_support_threshold = globals().get('support_threshold', 0.5)\n",
|
||||
"support_volume = np.zeros(volume.shape, dtype=bool)\n",
|
||||
"for index, original_layer in enumerate(volume):\n",
|
||||
" low, high = np.percentile(original_layer, (5, 95))\n",
|
||||
" if high == low:\n",
|
||||
" support_volume[index] = True\n",
|
||||
" else:\n",
|
||||
" scaled = np.clip((original_layer - low) / (high - low), 0, 1)\n",
|
||||
" support_volume[index] = scaled >= stack_support_threshold\n",
|
||||
"\n",
|
||||
"colored_stack = np.zeros((*volume.shape, 3), dtype=np.uint8)\n",
|
||||
"colored_stack[..., 0] = np.round(255 * second_red_volume * support_volume).astype(np.uint8) # second: red\n",
|
||||
"colored_stack[..., 1] = np.round(255 * first_blue_volume * support_volume).astype(np.uint8) # first: blue\n",
|
||||
"\n",
|
||||
"output_path = os.path.abspath('colored.tiff')\n",
|
||||
"tifffile.imwrite(output_path, colored_stack, photometric='rgb', metadata={'axes': 'ZYXS'})\n",
|
||||
"print(f'Saved {colored_stack.shape} RGB stack to {output_path}')\n",
|
||||
"\n",
|
||||
"reference_index = image.shape[0] // 2\n",
|
||||
"plt.figure(figsize=(6, 6))\n",
|
||||
"plt.imshow(colored_stack[reference_index])\n",
|
||||
"plt.title(f'Colored reference layer {reference_index}')\n",
|
||||
"plt.axis('off')\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 70,
|
||||
"id": "colored-alpha-stack",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"name": "stdout",
|
||||
"output_type": "stream",
|
||||
"text": [
|
||||
"Saved (32, 379, 379, 3) RGBA stack to /home/swim/sandbox/silicon-grain-boundaries/colored_alpha.tiff\n"
|
||||
]
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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H/zgB/zRH/0Rra2tRCIRVq9ezV/91V+VvOvtb387X/3qV/U75ae3t/eMfejv7+fNb34zTU1NhEIhVq9ezZe+9CVs29bXyHfXF7/4Rb785S+zePFiKioquOiii3jyySfPag6GhoZ497vfTWdnJ8FgkLa2Nm6++eaS9fRS2vKFL3yBz33ucyxatIhIJMLll1/OkSNHyOfz/NVf/RVtbW1UV1dz4403MjY2VtIWmed77rmHDRs2EA6HWbJkCf/0T/90WrvPpk3w4vLEfNn0xebqbOW6Mv4wUbaAl4FlWTz88MOcd955Z7Rad3Z2snnzZrZv345lWfh8Pv231772tbzhDW/gf/yP/3Hal5YX73jHO/jBD37ARz7yEa688koOHjzIjTfeyNzc3Fm1M5/Pc8MNN/Cud72LD33oQzz22GP87d/+LdXV1XziE5/Q1/X29vKe97yHrq4uQMW1/9mf/RlDQ0Ml1/0mOJt3/Pmf/zl33nkn3/ve97j11lv1vQcPHuThhx/WBzfAe97zHr797W/zgQ98gM997nNMTU1xxx13sHXrVvbt20dzc7O+dnh4mDe/+c185CMf4R/+4R8wzd9cj/b973+f9773vfzZn/0ZX/ziFzFNk2PHjnHw4EF9zcjICFu2bME0TT7xiU+wdOlSdu7cyd/93d/R29vLt771LUBZG175yldy6tQpPvOZz7BixQruvfdeXv/6159VW6ampgD45Cc/SUtLC4lEgnvuuYfLL7+chx56SCsUzqbNZ8Jvukb+z//5P3R3d/OVr3wF27b5/Oc/zzXXXMOjjz7KRRddVHLtTTfdxOtf/3re9a538dxzz/HRj34UgDvvvFNfc/z4cW655RYtBO7bt4+///u/59ChQyXXlVFGGWX8ujh58iRAiWX64YcfplAocMMNN7zk5zmOo5WvmUyG3bt385WvfIVt27axePHikmsHBwf5xS9+wU033URjYyNve9vb+Lu/+zsee+wxLrvsshd9z2te8xoeeughPvrRj3LJJZewf/9+PvnJT2oX+FAopK/fs2cPPT09/PVf/zWLFy8mFost+NxUKsVll13G4OAgH/vYx9iwYQMHDhzgE5/4BM899xy//OUvSwwJ9957L7t37+aOO+6goqKCz3/+89x4440cPnyYJUuWcOuttzI1NcX//t//mx/96Ee0trYCsGbNGgCOHj3Ktddey+23304sFuPQoUN87nOfY9euXWzfvh2Av/mbvyGZTHL33XeXhPHJs+ZjfHycrVu3ksvl+Nu//VsWLVrEz372Mz784Q9z/Phxvva1r5Vc/9WvfpVVq1bxla98Rb/v2muv5eTJk1p5shCGhoY4//zzyefzeqwmJyd54IEHmJ6eprm5+ddqy4YNG/jqV7/KzMwMH/rQh3j1q1/NBRdcQCAQ4M4776Svr48Pf/jD3HrrrfzkJz8puX/v3r3cfvvtfOpTn6KlpYXvfve7/Pmf/zm5XI4Pf/jDL2l8fh154sXm6qXIdWX8AcIp4w8eIyMjDuC84Q1veMHrXv/61zuAMzo66jiO43zyk590AOcTn/jEadfK3wQHDhxwAOcv//IvS677j//4Dwdw3va2t+nPHn74YQdwHn74Yf3Z2972Ngdw/vM//7Pk/muvvdZZuXLlGdtsWZaTz+edO+64w6mvr3ds29Z/u+yyy5zLLrvsBft8Nte92DvOOeeckutvu+02p6qqyonH447jOM7OnTsdwPnSl75Uct3AwIATiUScj3zkIyXPA5yHHnroRdv9Qm1/29ve5nR3d+vf3//+9zs1NTUv+Kz3vOc9TkVFhdPX11fy+Re/+EUHcA4cOOA4juN8/etfdwDnv//7v0uu+9M//VMHcL71rW+dVdsFhULByefzzite8QrnxhtvfEltPhu8lDVy8uRJB3Da2tqcdDqtP5+bm3Pq6uqcV77ylfoz2QOf//znS9733ve+1wmHwyXvWag93/nOdxyfz+dMTU39xn0so4wy/nDwrW99ywGcJ5980snn8048Hnd+/vOfOy0tLc6ll17q5PN5fe1nP/tZB3B+/vOfn/acfD5f8uNFd3e3A5z2s2XLFmd4ePi0Z91xxx0l7zlx4oRjGIbzlre85UX78/Of/3zBs/QHP/iBAzj/8i//UtIun8/nHD58+LTndHd3l8gan/nMZxzTNJ3du3eXXHf33Xc7gHPffffpzwCnubnZmZub05+NjIw4pmk6n/nMZ/RnX/jCFxzAOXny5Av2ybZtJ5/PO48++qgDOPv27dN/e9/73uecSTyf34e/+qu/cgDnqaeeKrnutttucwzD0OMg313r1693CoWCvm7Xrl0O4PzHf/zHC7b3ne98pxMIBJyDBw+e8ZqX2paNGzc6lmXp677yla84gHPDDTeU3H/77bc7gDM7O1syDoZhOHv37i259qqrrnKqqqqcZDL5ktp0NvLEQrLpmebqpch1ZfxhouyCXsZZw3Fdxea7lt90000veu+jjz4KwOte97qSz2+++ebTXN7PBMMwePWrX13y2YYNG+jr6yv5bPv27bzyla+kuroan89HIBDgE5/4BJOTk6e5Mf2Line truncated
|
||||
"text/plain": [
|
||||
"<Figure size 1000x500 with 2 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"# RGBA version: use the original TIFF intensity as continuous alpha, not a cutoff mask.\n",
|
||||
"# Run the two orientation-filter cells first. Output: ./colored_alpha.tiff\n",
|
||||
"volume = image.astype(float)\n",
|
||||
"fft_volume = np.fft.fftshift(np.fft.fft2(volume, axes=(-2, -1)), axes=(-2, -1))\n",
|
||||
"first_volume = np.fft.ifft2(\n",
|
||||
" np.fft.ifftshift(fft_volume * si110_grid_mask[None, :, :], axes=(-2, -1)), axes=(-2, -1)\n",
|
||||
").real\n",
|
||||
"second_volume = np.fft.ifft2(\n",
|
||||
" np.fft.ifftshift(fft_volume * second_mask[None, :, :], axes=(-2, -1)), axes=(-2, -1)\n",
|
||||
").real\n",
|
||||
"\n",
|
||||
"def normalize_for_display(values, percentiles=(1, 99.5)):\n",
|
||||
" low, high = np.percentile(values, percentiles)\n",
|
||||
" if high == low:\n",
|
||||
" return np.zeros_like(values)\n",
|
||||
" return np.clip((values - low) / (high - low), 0, 1)\n",
|
||||
"\n",
|
||||
"# RGB encodes filtered orientation strength; alpha continuously follows original intensity.\n",
|
||||
"first_blue_volume = normalize_for_display(np.abs(first_volume))\n",
|
||||
"second_red_volume = normalize_for_display(np.abs(second_volume))\n",
|
||||
"original_alpha = beta.cdf(normalize_for_display(volume), 3, 8)\n",
|
||||
"\n",
|
||||
"colored_alpha_stack = np.zeros((*volume.shape, 3), dtype=np.uint8)\n",
|
||||
"colored_alpha_stack[..., 0] = np.round(255 * (second_red_volume * (original_alpha))).astype(np.uint8) # second: red\n",
|
||||
"colored_alpha_stack[..., 1] = np.round(255 * (first_blue_volume * (original_alpha))).astype(np.uint8) # first: blue\n",
|
||||
"# colored_alpha_stack[..., 2] = np.ones_like(original_alpha) * 255\n",
|
||||
"# colored_alpha_stack[..., 3] = np.round(255 * (original_alpha)).astype(np.uint8) # original: alpha\n",
|
||||
"\n",
|
||||
"output_path = os.path.abspath('colored_alpha.tiff')\n",
|
||||
"tifffile.imwrite(\n",
|
||||
" output_path, colored_alpha_stack, photometric='rgb', extrasamples='unassalpha',\n",
|
||||
" metadata={'axes': 'ZYXS'},\n",
|
||||
")\n",
|
||||
"print(f'Saved {colored_alpha_stack.shape} RGBA stack to {output_path}')\n",
|
||||
"\n",
|
||||
"reference_index = image.shape[0] // 2\n",
|
||||
"fig, axes = plt.subplots(1, 2, figsize=(10, 5), constrained_layout=True)\n",
|
||||
"axes[0].imshow(original_alpha[reference_index], cmap='gray', vmin=0, vmax=1)\n",
|
||||
"axes[0].set_title('Original layer used as alpha')\n",
|
||||
"axes[1].imshow(colored_alpha_stack[reference_index])\n",
|
||||
"axes[1].set_title('RGBA orientation composite')\n",
|
||||
"for ax in axes:\n",
|
||||
" ax.axis('off')\n",
|
||||
"plt.show()"
|
||||
]
|
||||
},
|
||||
{
|
||||
"cell_type": "code",
|
||||
"execution_count": 69,
|
||||
"id": "05c7b076",
|
||||
"metadata": {},
|
||||
"outputs": [
|
||||
{
|
||||
"data": {
|
||||
"text/plain": [
|
||||
"[<matplotlib.lines.Line2D at 0x7f03c9cd2270>]"
|
||||
]
|
||||
},
|
||||
"execution_count": 69,
|
||||
"metadata": {},
|
||||
"output_type": "execute_result"
|
||||
},
|
||||
{
|
||||
"data": {
|
||||
"image/png": "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 truncated
|
||||
"text/plain": [
|
||||
"<Figure size 640x480 with 1 Axes>"
|
||||
]
|
||||
},
|
||||
"metadata": {},
|
||||
"output_type": "display_data"
|
||||
}
|
||||
],
|
||||
"source": [
|
||||
"from scipy.stats import beta\n",
|
||||
"\n",
|
||||
"x = np.linspace(0, 1)\n",
|
||||
"\n",
|
||||
"plt.plot(x, beta.cdf(x, 4, 8))"
|
||||
]
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"kernelspec": {
|
||||
"display_name": "lemon",
|
||||
"language": "python",
|
||||
"name": "python3"
|
||||
},
|
||||
"language_info": {
|
||||
"codemirror_mode": {
|
||||
"name": "ipython",
|
||||
"version": 3
|
||||
},
|
||||
"file_extension": ".py",
|
||||
"mimetype": "text/x-python",
|
||||
"name": "python",
|
||||
"nbconvert_exporter": "python",
|
||||
"pygments_lexer": "ipython3",
|
||||
"version": "3.12.12"
|
||||
}
|
||||
},
|
||||
"nbformat": 4,
|
||||
"nbformat_minor": 5
|
||||
}
|
||||
Reference in new issue
Block a user