{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":84969,"databundleVersionId":10033515,"sourceType":"competition"}],"dockerImageVersionId":30839,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"## Installing packages\n> Since these are zarr file for tomogram data, we need to use zarr, ome-zarr and copick package\n> The Information in Data tab also suggests the same","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19"}},{"cell_type":"code","source":"!pip install -q zarr ome-zarr copick","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:58:46.515542Z","iopub.execute_input":"2025-01-28T02:58:46.515884Z","iopub.status.idle":"2025-01-28T02:59:00.463763Z","shell.execute_reply.started":"2025-01-28T02:58:46.515857Z","shell.execute_reply":"2025-01-28T02:59:00.462681Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Loading Libraries","metadata":{}},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport zarr\nfrom pathlib import Path","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:00.465316Z","iopub.execute_input":"2025-01-28T02:59:00.465670Z","iopub.status.idle":"2025-01-28T02:59:00.957663Z","shell.execute_reply.started":"2025-01-28T02:59:00.465628Z","shell.execute_reply":"2025-01-28T02:59:00.956580Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Reading data","metadata":{}},{"cell_type":"markdown","source":"### As per the data, we have tomogram data represented by zarr filesets and there is also particle.json file which is data about each particle.\n\n### Reading data ways\n- `zarr files`: Using zarr package\n- `json files`:  Its a json format so we can read it through json package.\n","metadata":{}},{"cell_type":"markdown","source":"### Reading tomogram data","metadata":{}},{"cell_type":"code","source":"train_zarr_path = Path('/kaggle/input/czii-cryo-et-object-identification/train/static/ExperimentRuns/TS_5_4/VoxelSpacing10.000/denoised.zarr')\ntrain_zarr_data = zarr.open(str(train_zarr_path))\n\n# printing data about zarr store\nprint(\"Zarr file contains:\")\nprint(train_zarr_data.tree())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:00.959606Z","iopub.execute_input":"2025-01-28T02:59:00.960229Z","iopub.status.idle":"2025-01-28T02:59:01.115057Z","shell.execute_reply.started":"2025-01-28T02:59:00.960193Z","shell.execute_reply":"2025-01-28T02:59:01.114003Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"There are three scales and the first one is with the highest resolution","metadata":{}},{"cell_type":"code","source":"tomogram_ts_5_4 = train_zarr_data[0][:]\n\nprint(f\"Tomogram shape: {tomogram_ts_5_4.shape}\")\nprint(f\"Tomogram DataType: {tomogram_ts_5_4.dtype}\")\nprint(f\"Tomogram minimum value: {tomogram_ts_5_4.min()}\")\nprint(f\"Tomogram maximum value: {tomogram_ts_5_4.max()}\")\nprint(f\"Tomogram Mean value: {tomogram_ts_5_4.mean()}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:01.116659Z","iopub.execute_input":"2025-01-28T02:59:01.117037Z","iopub.status.idle":"2025-01-28T02:59:04.422840Z","shell.execute_reply.started":"2025-01-28T02:59:01.117000Z","shell.execute_reply":"2025-01-28T02:59:04.421761Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Reading first apo feritin particle JSON data","metadata":{}},{"cell_type":"code","source":"import json\n\napo_ferritin_json_path = Path('/kaggle/input/czii-cryo-et-object-identification/train/overlay/ExperimentRuns/TS_5_4/Picks/apo-ferritin.json')\n\nwith open(apo_ferritin_json_path, 'r') as f:\n    data = json.load(f)\n\n# Visualize the structure\nprint(\"Keys in the JSON file: \", data.keys())\nprint(\"First few points:\");\nprint(json.dumps(data['points'][:2], indent=2))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:04.423948Z","iopub.execute_input":"2025-01-28T02:59:04.424334Z","iopub.status.idle":"2025-01-28T02:59:04.442631Z","shell.execute_reply.started":"2025-01-28T02:59:04.424298Z","shell.execute_reply":"2025-01-28T02:59:04.441558Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> From above we can say that there are points in the 3dimension space and is represented in location key","metadata":{}},{"cell_type":"markdown","source":"## Getting the range of the XYZ coordinates in the apo-ferritin particle","metadata":{}},{"cell_type":"code","source":"apo_data = data\ncoords = list()\n# inside each point we have location\nfor point in apo_data[\"points\"]:\n    x, y, z = (point[\"location\"][key] for key in ['x', 'y', 'z'])\n    coords.append([x, y, z])\ncoords = np.array(coords)\nprint(f\"Total points coordinates: {len(coords)}\")\nprint(f\"X range: {coords[:,0].min()} to {coords[0].max()}\")\nprint(f\"Y range: {coords[:,1].min()} to {coords[1].max()}\")\nprint(f\"Z range: {coords[:,2].min()} to {coords[2].max()}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:04.443614Z","iopub.execute_input":"2025-01-28T02:59:04.443954Z","iopub.status.idle":"2025-01-28T02:59:04.451417Z","shell.execute_reply.started":"2025-01-28T02:59:04.443926Z","shell.execute_reply":"2025-01-28T02:59:04.450483Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"> As per the above few observations, we can say that the scales of tomogram and the particle does not match and we need to scale or normalize the particles range to match the tomogram shape. Therefore the next step should be feature scaling or scaling the coordinates range","metadata":{}},{"cell_type":"code","source":"# tomogram shape\ntomogram_ts_5_4.shape","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:04.452340Z","iopub.execute_input":"2025-01-28T02:59:04.452685Z","iopub.status.idle":"2025-01-28T02:59:04.470478Z","shell.execute_reply.started":"2025-01-28T02:59:04.452648Z","shell.execute_reply":"2025-01-28T02:59:04.469378Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### The particle's range should match the above i.e it should range till \n> - What are the dimensions in tomogram data represent\n    - First dimension (184): This represents the depth which is the z coordinate\n    - Second Dimension (630): This represents rows in the image slices which is the y coordinate\n    - Third dimension (630): This represents columns in the image slices which is the x coordinate\n\n- So our particle ranges\n\n- Z coordinate should range till max of 184\n\n- Y and X coordinate should range till max of 630\n\n- To scale this way, we can simply divide Z with 184, X and Y with 630","metadata":{}},{"cell_type":"markdown","source":"## Scaling the coordinates","metadata":{}},{"cell_type":"code","source":"def scale_coordinates(coords, tomogram_shape):\n    scaled_coords = coords.copy()\n    \n    # Scale factors for each dimension\n    scale_z = tomogram_shape[0] / coords[:, 2].max() # 184 / 5744.509\n    scale_y = tomogram_shape[1] / coords[:, 1].max()\n    scale_x = tomogram_shape[2] / coords[:, 0].max()\n    \n    # Apply scaling\n    scaled_coords[:, 2] = coords[:, 2] * scale_z\n    scaled_coords[:, 1] = coords[:, 1] * scale_y\n    scaled_coords[:, 0] = coords[:, 0] * scale_x\n    \n    return scaled_coords\n\n# Scale the coordinates\nscaled_coords = scale_coordinates(coords, tomogram_ts_5_4.shape)\n\nprint(\"\\nCoordinate ranges after scaling:\")\nprint(f\"X range: {scaled_coords[:, 0].min():.1f} to {scaled_coords[:, 0].max():.1f}\")\nprint(f\"Y range: {scaled_coords[:, 1].min():.1f} to {scaled_coords[:, 1].max():.1f}\")\nprint(f\"Z range: {scaled_coords[:, 2].min():.1f} to {scaled_coords[:, 2].max():.1f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:04.471651Z","iopub.execute_input":"2025-01-28T02:59:04.472039Z","iopub.status.idle":"2025-01-28T02:59:04.487825Z","shell.execute_reply.started":"2025-01-28T02:59:04.472012Z","shell.execute_reply":"2025-01-28T02:59:04.486828Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualizing the apo ferritin particle","metadata":{}},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\n\ndef visualize_apo_ferritin_particle(tomogram, coords, n_slices=3, slice_thickness=10):\n    \"\"\"\n    Visualizing apo-ferritin particles in tomogram slices using subplots.\n    \n    Parameters:\n        tomogram (ndarray): 3D tomogram array.\n        coords (ndarray): Array of particle coordinates [x, y, z].\n        n_slices (int): Number of slices to display.\n        slice_thickness (int): Range around each slice to include particles.\n    \"\"\"\n    # Normalize tomogram for consistent visualization\n    vmin, vmax = np.percentile(tomogram, (1, 99))\n    normalized_tomogram = np.clip((tomogram - vmin) / (vmax - vmin), 0, 1)\n\n    # Calculate z-positions for the slices\n    z_positions = np.linspace(0, tomogram.shape[0] - 1, n_slices, dtype=int)\n\n    # Create subplots\n    fig, axes = plt.subplots(1, n_slices, figsize=(20, 8), constrained_layout=True)\n    \n    for idx, ax in enumerate(axes):\n        z = z_positions[idx]\n\n        # Display the tomogram slice\n        im = ax.imshow(normalized_tomogram[z, :, :], cmap='gray', vmin=0, vmax=1)\n        \n        # Highlight particles near the current slice\n        mask = np.abs(coords[:, 2] - z) < slice_thickness\n        if np.any(mask):\n            ax.scatter(\n                coords[mask, 0], coords[mask, 1],\n                color='red', marker='o', s=100,\n                facecolors='none', linewidth=2,\n                label='apo-ferritin'\n            )\n        \n        # Customize the subplot\n        ax.set_title(f\"Slice Z={z}\\n({np.sum(mask)} particles visible)\")\n        ax.set_xlim(0, tomogram.shape[2])\n        ax.set_ylim(tomogram.shape[1], 0)  # Invert y-axis for correct orientation\n        ax.axis('off')  # Turn off axes for cleaner visualization\n    \n    # Add a single colorbar for the figure\n    cbar = fig.colorbar(im, ax=axes, location='right', shrink=0.7, pad=0.05)\n    cbar.set_label('Normalized Intensity', fontsize=12)\n\n    # Add a shared title\n    fig.suptitle(\n        f\"Apo-ferritin Particles in Tomogram Slices\\n\"\n        f\"Displaying particles within ±{slice_thickness} units of each slice\",\n        fontsize=16\n    )\n\n    # Show legend on the first subplot\n    axes[0].legend(loc='upper right')\n    \n    plt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:04.490080Z","iopub.execute_input":"2025-01-28T02:59:04.490371Z","iopub.status.idle":"2025-01-28T02:59:04.511359Z","shell.execute_reply.started":"2025-01-28T02:59:04.490345Z","shell.execute_reply":"2025-01-28T02:59:04.510322Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Create the visualization with scaled coordinates\nvisualize_apo_ferritin_particle(tomogram_ts_5_4, scaled_coords)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:04.512881Z","iopub.execute_input":"2025-01-28T02:59:04.513189Z","iopub.status.idle":"2025-01-28T02:59:07.671337Z","shell.execute_reply.started":"2025-01-28T02:59:04.513162Z","shell.execute_reply":"2025-01-28T02:59:07.669928Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Loading all the particle coordinates. \n - For each particle, we will combine all the coordinates","metadata":{}},{"cell_type":"code","source":"PARTICLE_MAP = {\n    'apo-ferritin': {'color': '#FF3333', 'marker': 'o', 'difficulty': 'easy'},          # Bright red\n    'beta-amylase': {'color': '#FFFFFF', 'marker': 's', 'difficulty': 'impossible'},     # White\n    'beta-galactosidase': {'color': '#33FFFF', 'marker': '^', 'difficulty': 'hard'},    # Cyan\n    'ribosome': {'color': '#33FF33', 'marker': 'D', 'difficulty': 'easy'},              # Bright green\n    'thyroglobulin': {'color': '#FF33FF', 'marker': 'p', 'difficulty': 'hard'},         # Magenta\n    'virus-like-particle': {'color': '#FFFF33', 'marker': '*', 'difficulty': 'easy'}     # Yellow\n}\n\nEXPERIMENT_NAMES = [\"TS_5_4\", \"TS_69_2\", \"TS_6_4\", \"TS_6_6\", \"TS_73_6\", \"TS_86_3\", \"TS_99_9\"]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:07.672961Z","iopub.execute_input":"2025-01-28T02:59:07.673281Z","iopub.status.idle":"2025-01-28T02:59:07.678909Z","shell.execute_reply.started":"2025-01-28T02:59:07.673251Z","shell.execute_reply":"2025-01-28T02:59:07.677837Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Loading particles for experiment TS_5_4","metadata":{}},{"cell_type":"code","source":"def load_particle_coordinates(experiment_name=\"TS_5_4\"):\n    base_path = Path(\"/kaggle/input/czii-cryo-et-object-identification/train/overlay/ExperimentRuns\")\n    particle_coords = {}\n    print(\"-\" * 50)\n    for particle in PARTICLE_MAP:\n        particle_path = base_path / experiment_name / 'Picks' / f'{particle}.json'\n        try:\n            with open(particle_path, 'r') as f:\n                data = json.load(f)\n                coords = list()\n                # inside each point we have location\n                for point in data[\"points\"]:\n                    x, y, z = (point[\"location\"][key] for key in ['x', 'y', 'z'])\n                    coords.append([x, y, z])\n                coords = np.array(coords)\n                particle_coords[particle] = coords\n                print(f\"Loaded {len(coords)} {particle} coordinates\")\n        except Exception as e:\n            print(f\"Error reading {particle} coordinates: {e}\")\n            particle_coords[particle] = np.array([])\n    return particle_coords","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:07.680048Z","iopub.execute_input":"2025-01-28T02:59:07.680435Z","iopub.status.idle":"2025-01-28T02:59:07.699183Z","shell.execute_reply.started":"2025-01-28T02:59:07.680379Z","shell.execute_reply":"2025-01-28T02:59:07.698095Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualizing all the particle coordinates\n\nFor doing the above we need\n- all the particle coordinates for an experiment\n- scale all these coordinates properly to tomogram shape\n- visualize","metadata":{}},{"cell_type":"code","source":"def visualize_all_particles(tomogram, particle_coords, experiment_name=\"TS_5_4\", print_statistics=True, n_slices=3, slice_thickness=10):\n    \"\"\"\n    Visualizing all particles in tomogram slices using subplots.\n    \n    Parameters:\n        tomogram (ndarray): 3D tomogram array.\n        coords (ndarray): Array of particle coordinates [x, y, z].\n        n_slices (int): Number of slices to display.\n        slice_thickness (int): Range around each slice to include particles.\n    \"\"\"\n    # Normalize tomogram for consistent visualization\n    vmin, vmax = np.percentile(tomogram, (1, 99))\n    normalized_tomogram = np.clip((tomogram - vmin) / (vmax - vmin), 0, 1)\n\n    # Calculate z-positions for the slices\n    all_z_coords = []\n    for coords in particle_coords.values():\n        if len(coords):\n            all_z_coords.extend(coords[:, 2])\n    \n    if all_z_coords:\n        z_coords = np.array(all_z_coords)\n        z_density = np.histogram(z_coords, bins=50)[0]\n        highest_density_indices = np.argsort(z_density)[-n_slices:]\n        z_positions = np.linspace(z_coords.min(), z_coords.max(), 51)[highest_density_indices]\n    else:\n        z_positions = np.linspace(0, tomogram.shape[0]-1, n_slices, dtype=int)\n    \n\n    # Create subplots\n    fig, axes = plt.subplots(1, n_slices, figsize=(20, 8), constrained_layout=True)\n    \n    for idx, ax in enumerate(axes):\n        z = int(z_positions[idx])\n\n        # Display the tomogram slice\n        im = ax.imshow(normalized_tomogram[z, :, :], cmap='gray', vmin=0, vmax=1)\n\n        # plot each particle type\n        particles_in_slice = 0\n        particle_counts = {}\n        for particle_type, coords in particle_coords.items():\n            if len(coords):\n                \n        \n                # Highlight particles near the current slice\n                mask = np.abs(coords[:, 2] - z) < slice_thickness\n                if np.any(mask):\n                    style = PARTICLE_MAP[particle_type]\n                    ax.scatter(\n                        coords[mask, 0], coords[mask, 1],\n                        color=style['color'], marker=style['marker'], s=100,\n                        facecolors='none', linewidth=2,\n                        label=f\"{particle_type}\\n({style['difficulty']})\"\n                    )\n                    count = np.sum(mask)\n                    particles_in_slice += count\n                    particle_counts[particle_type] = count \n        \n        # Customize the subplot\n        # Create detailed title showing counts for each particle type\n        title_parts = [f'Slice Z={z}']\n        if particle_counts:\n            for ptype, count in particle_counts.items():\n                if count > 0:\n                    title_parts.append(f'{ptype}: {count}')\n        title = '\\n'.join(title_parts)\n        ax.set_title(title, fontsize=8)\n\n        \n        ax.set_xlim(0, tomogram.shape[2])\n        ax.set_ylim(tomogram.shape[1], 0)  # Invert y-axis for correct orientation\n        ax.axis('off')  # Turn off axes for cleaner visualization\n\n        # Add legend with semi-transparent background for better visibility\n        if idx == 0:  # Only add legend to first subplot\n            handles, labels = ax.get_legend_handles_labels()\n            legend = ax.legend(handles, labels,\n                             bbox_to_anchor=(0.02, 0.98), \n                             loc='upper left',\n                             borderaxespad=0.,\n                             framealpha=0.8,\n                             facecolor='black',\n                             edgecolor='white',\n                             labelcolor='white',\n                             fontsize=8)\n            \n            for handle in handles:\n                handle.set_edgecolor('black')\n                handle.set_linewidth(1.5)\n    \n    # Add a single colorbar for the figure\n    cbar = fig.colorbar(im, ax=axes, location='right', shrink=0.7, pad=0.05)\n    cbar.set_label('Normalized Intensity', fontsize=12)\n\n    # Add a shared title\n    fig.suptitle(f'All Particle Types for experiment {experiment_name} in Tomogram Slices\\n' + \n             f'Showing particles within ±{slice_thickness} units of each slice',\n             fontsize=16, y=1.05)\n\n    # Show legend on the first subplot\n    axes[0].legend(loc='upper right')\n    \n    plt.show()\n    if print_statistics:\n        # Print overall particle statistics\n        print(\"\\nOverall Particle Statistics:\")\n        print(\"-\" * 50)\n        for particle_type, coords in particle_coords.items():\n            if len(coords) > 0:\n                print(f\"\\n{particle_type} ({PARTICLE_MAP[particle_type]['difficulty']}):\")\n                print(f\"Total particles: {len(coords)}\")\n                print(f\"Z range: {coords[:, 2].min():.1f} to {coords[:, 2].max():.1f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:07.700377Z","iopub.execute_input":"2025-01-28T02:59:07.700766Z","iopub.status.idle":"2025-01-28T02:59:07.719519Z","shell.execute_reply.started":"2025-01-28T02:59:07.700735Z","shell.execute_reply":"2025-01-28T02:59:07.718466Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"all_particle_coords = load_particle_coordinates()\nscale_particle_coords = { particle_type: scale_coordinates(coords, tomogram_ts_5_4.shape)\n    for particle_type, coords in all_particle_coords.items() }\nvisualize_all_particles(tomogram_ts_5_4, scale_particle_coords)\n# Print statistics for each particle type\nprint(\"\\nParticle Statistics:\")\nprint(\"-\" * 50)\nfor particle_type, coords in scale_particle_coords.items():\n    if len(coords) > 0:\n        print(f\"\\n{particle_type} ({PARTICLE_MAP[particle_type]['difficulty']}):\")\n        print(f\"Number of particles: {len(coords)}\")\n        print(f\"X range: {coords[:, 0].min():.1f} to {coords[:, 0].max():.1f}\")\n        print(f\"Y range: {coords[:, 1].min():.1f} to {coords[:, 1].max():.1f}\")\n        print(f\"Z range: {coords[:, 2].min():.1f} to {coords[:, 2].max():.1f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:07.720571Z","iopub.execute_input":"2025-01-28T02:59:07.720960Z","iopub.status.idle":"2025-01-28T02:59:10.953013Z","shell.execute_reply.started":"2025-01-28T02:59:07.720923Z","shell.execute_reply":"2025-01-28T02:59:10.951967Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Visualize for other experiments","metadata":{}},{"cell_type":"code","source":"# FIRST findout all the tomogram shapes for each experiment\n# we already have tomogram data for ts_5_4\nEXPERIMENT_MAP = {} # name: tomogram\nfor experiment_name in EXPERIMENT_NAMES:\n    train_zarr_path = Path(f'/kaggle/input/czii-cryo-et-object-identification/train/static/ExperimentRuns/{experiment_name}/VoxelSpacing10.000/denoised.zarr')\n    train_zarr_data = zarr.open(str(train_zarr_path))\n    \n    # printing data about zarr store\n    print(f\"{experiment_name} Zarr file contains:\")\n    print(train_zarr_data.tree())\n    tomogram = train_zarr_data[0][:]\n    EXPERIMENT_MAP[experiment_name] = tomogram\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:10.953919Z","iopub.execute_input":"2025-01-28T02:59:10.954194Z","iopub.status.idle":"2025-01-28T02:59:24.185535Z","shell.execute_reply.started":"2025-01-28T02:59:10.954171Z","shell.execute_reply":"2025-01-28T02:59:24.184524Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# visualizing\nfor experiment_name, tomogram in EXPERIMENT_MAP.items():\n    all_particle_coords = load_particle_coordinates(experiment_name)\n    scale_particle_coords = { particle_type: scale_coordinates(coords, tomogram.shape)\n        for particle_type, coords in all_particle_coords.items() }\n    visualize_all_particles(tomogram, scale_particle_coords, experiment_name, print_statistics=False)\n    # Print statistics for each particle type\n    print(f\"\\nParticle Statistics for {experiment_name}:\")\n    print(\"-\" * 50)\n    for particle_type, coords in scale_particle_coords.items():\n        if len(coords) > 0:\n            print(f\"\\n{particle_type} ({PARTICLE_MAP[particle_type]['difficulty']}):\")\n            print(f\"Number of particles: {len(coords)}\")\n            print(f\"X range: {coords[:, 0].min():.1f} to {coords[:, 0].max():.1f}\")\n            print(f\"Y range: {coords[:, 1].min():.1f} to {coords[:, 1].max():.1f}\")\n            print(f\"Z range: {coords[:, 2].min():.1f} to {coords[:, 2].max():.1f}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-01-28T02:59:24.186490Z","iopub.execute_input":"2025-01-28T02:59:24.186867Z","iopub.status.idle":"2025-01-28T02:59:47.043133Z","shell.execute_reply.started":"2025-01-28T02:59:24.186820Z","shell.execute_reply":"2025-01-28T02:59:47.042042Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Reference\n- https://www.kaggle.com/code/rmsbms/eda-particles-visualization","metadata":{}}]}