{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","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"},{"sourceId":9835976,"sourceType":"datasetVersion","datasetId":6033396}],"dockerImageVersionId":30786,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install zarr","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport zarr\nimport json\nimport os\nimport random\n\nimport torch\nimport torch.optim as optim\nfrom torch.utils.data import DataLoader, TensorDataset\nfrom sklearn.preprocessing import StandardScaler\nfrom tqdm import tqdm\nfrom mpl_toolkits.axes_grid1 import ImageGrid\nimport numpy as np\nimport plotly.graph_objects as go\n\nimport matplotlib.pyplot as plt\nfrom joblib import Parallel, delayed\n# Setting seed for reproducibility\ndef seed_everything(seed=42):\n    random.seed(seed)\n    np.random.seed(seed)\n    # Add other libraries as needed, e.g., TensorFlow or PyTorch:\n    import torch\n    torch.manual_seed(seed)\n    if torch.cuda.is_available():\n        torch.cuda.manual_seed_all(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = False\nseed_everything(42)\n\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def load_tomogram(experiment_path):\n    tomogram = zarr.open(experiment_path, mode='r')\n    return tomogram[0]  # Assuming 0 is the highest resolution\n\ndef load_particle_locations(json_path):\n    with open(json_path, 'r') as f:\n        data = json.load(f)\n    if 'points' in data:\n        return np.array(data['points'])\n    else:\n        print(f\"Key 'points' not found in {json_path}. Available keys: {data.keys()}\")\n        return np.array([])","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_path = '/kaggle/input/czii-cryo-et-object-identification/train/'\ntrain_static = train_path+'static/ExperimentRuns/'\noverlay = train_path+'overlay/ExperimentRuns/'\ntest_path = '/kaggle/input/czii-cryo-et-object-identification/test/'\ntest_static = test_path + 'static/ExperimentRuns/'\nsample_submission_path = '/kaggle/input/czii-cryo-et-object-identification/sample_submission.csv'\n\ntomogram_shape = (184, 630, 630)\ncube_size = (1,630,630)\nres = '0'\nPARTICLE_TYPES = {\n    'apo-ferritin': {'color': '#FF3333', 'marker': 'o', 'difficulty': 'easy','class':0},          # Bright red\n    'beta-galactosidase': {'color': '#33FFFF', 'marker': '^', 'difficulty': 'hard','class':1},    # Cyan\n    'ribosome': {'color': '#33FF33', 'marker': 'D', 'difficulty': 'easy','class':2},              # Bright green\n    'thyroglobulin': {'color': '#FF33FF', 'marker': 'p', 'difficulty': 'hard','class':3},         # Magenta\n    'virus-like-particle': {'color': '#FFFF33', 'marker': '*', 'difficulty': 'easy','class':4}     # Yellow\n}\nclass_particle = {0:'apo-ferritin',\n                 1:'beta-galactosidase',\n                 2:'ribosome',\n                 3:'thyroglobulin',\n                 4:'virus-like-particle',\n                 5:'None'}","metadata":{"execution":{"iopub.status.busy":"2024-11-07T16:51:31.176628Z","iopub.execute_input":"2024-11-07T16:51:31.177055Z","iopub.status.idle":"2024-11-07T16:51:31.214236Z","shell.execute_reply.started":"2024-11-07T16:51:31.177004Z","shell.execute_reply":"2024-11-07T16:51:31.213164Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_experiments = os.listdir(train_static)\ntest_experiments = os.listdir(test_static)\n\nprint(train_experiments)\nparticle_types = ['apo-ferritin', 'beta-galactosidase', 'ribosome', 'thyroglobulin', 'virus-like-particle'] # no beta-amylase\n\nX_train = []\ny_train_x = []\ny_train_y = []\ny_train_z = []","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def generate_cube_centers(cube_size=cube_size):\n    cube_idx_to_center = {}\n    max_z, max_y, max_x = 184, 630, 630\n    for cube_z in range(int(max_z // cube_size[0]) + 1):\n        for cube_y in range(int(max_y // cube_size[1]) + 1):\n            for cube_x in range(int(max_x // cube_size[2]) + 1):\n                cube_key = (cube_z, cube_y, cube_x)\n                cube_center = [\n                    (cube_z + 0.5) * cube_size[0],\n                    (cube_y + 0.5) * cube_size[1],\n                    (cube_x + 0.5) * cube_size[2]\n                ]\n                cube_idx_to_center[cube_key] = cube_center\n    return cube_idx_to_center\ncube_idx_to_center = generate_cube_centers(cube_size = cube_size)\nlen(cube_idx_to_center)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"os.makedirs(f'CryoET-{cube_size}',exist_ok = True)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def scale_coordinates(coords, tomogram_shape = (184, 630, 630)):\n    \"\"\"Scale coordinates to match tomogram dimensions.\"\"\"\n    scaled_coords = coords.copy()\n    \n    # Scale factors for each dimension\n    scale_z = tomogram_shape[0] / coords[:, 0].max()\n    scale_y = tomogram_shape[1] / coords[:, 1].max()\n    scale_x = tomogram_shape[2] / coords[:, 2].max()\n    \n    # Apply scaling\n    scaled_coords[:, 0] = coords[:, 0] * scale_z\n    scaled_coords[:, 1] = coords[:, 1] * scale_y\n    scaled_coords[:, 2] = coords[:, 2] * scale_x\n    \n    return scaled_coords\ndef load_denoise(experiment, res = res):\n    experiment_path = os.path.join(train_static,experiment,'VoxelSpacing10.000/denoised.zarr')\n    experiment_zarr = zarr.open(experiment_path)\n    return experiment_zarr[res][:]\ndef load_denoise_cube(experiment, cube_idx, cube_idx_to_center=cube_idx_to_center, cube_size=cube_size, res=res):\n    experiment_path = os.path.join(train_static, experiment, 'VoxelSpacing10.000/denoised.zarr')\n    experiment_zarr = zarr.open(experiment_path)\n    tomogram = experiment_zarr[res][:]\n\n    # Extract the cube coordinates from the cube_idx\n    cube_z, cube_y, cube_x = cube_idx\n    start_z = cube_z * cube_size[0]\n    start_y = cube_y * cube_size[1]\n    start_x = cube_x * cube_size[2]\n\n    if (start_z + cube_size[0] > tomogram.shape[0] or\n        start_y + cube_size[1] > tomogram.shape[1] or\n        start_x + cube_size[2] > tomogram.shape[2]):\n        print('Out of bound')\n        return None\n\n    # Extract the cube from the tomogram\n    cube = tomogram[start_z:start_z + cube_size[0], \n                    start_y:start_y + cube_size[1], \n                    start_x:start_x + cube_size[2]]\n    return cube\n\ndef load_coordinate(experiment, particles='apo-ferritin'):\n    coordinate_path = os.path.join(overlay,experiment,f'Picks/{particles}.json')\n    with open(coordinate_path, 'r') as f:\n        data = json.load(f)\n\n    # Extract coordinates from the points array\n    coords = []\n    for point in data['points']:\n        coords.append([\n            point['location']['z'],\n            point['location']['y'],\n            point['location']['x']\n        ])\n\n    coords = np.array(coords)\n    coords = scale_coordinates(coords)\n#     print(f\"{experiment}===Loaded {len(coords)} {particles} coordinates\")\n    return coords\ndef process_coordinate(coord, experiment, particles, cube_size, res):\n    # Determine which cube the coordinate belongs to\n    z, y, x = coord\n    cube_z = int(z // cube_size[0])\n    cube_y = int(y // cube_size[1])\n    cube_x = int(x // cube_size[2])\n    cube_key = (cube_z, cube_y, cube_x)\n    \n    # Load the cube and save if valid\n    cube_ = load_denoise_cube(experiment, cube_key, cube_idx_to_center, cube_size, res=res)\n    if cube_ is not None:\n        X = cube_\n        y = PARTICLE_TYPES[particles]['class']\n        \n        # Create filename based on experiment, particles, and cube position\n        filename = f\"{experiment}-{particles}-{cube_z}-{cube_y}-{cube_x}.npz\"\n        save_path = os.path.join(f'/kaggle/working/CryoET-{cube_size}', filename)\n        \n        # Save to .npz file\n        np.savez(save_path, X=X, y=y)\n        \n        # Return the used cube key if it was saved successfully\n        return cube_key\n    return None\ndef load_cube_coordinate(experiment, particles='apo-ferritin', cube_size=cube_size, res='0'):\n    coordinate_path = os.path.join(overlay, experiment, f'Picks/{particles}.json')\n    with open(coordinate_path, 'r') as f:\n        data = json.load(f)\n\n    # Extract coordinates from the points array\n    coords = []\n    for point in data['points']:\n        coords.append([\n            point['location']['z'],\n            point['location']['y'],\n            point['location']['x']\n        ])\n\n    coords = np.array(coords)\n    coords = scale_coordinates(coords)\n\n    # Process coordinates in parallel\n    used_cube = Parallel(n_jobs=-1)(\n        delayed(process_coordinate)(coord, experiment, particles, cube_size, res) for coord in coords\n    )\n\n    # Filter out None values from used_cube\n    used_cube = [cube_key for cube_key in used_cube if cube_key is not None]\n    return used_cube\n\ndef load_all_particle_coordinates(experiment='TS_5_4'):\n    \"\"\"Load coordinates for all particle types.\"\"\"\n    particle_coords = {}\n    \n    for particle_type in PARTICLE_TYPES.keys():\n        k = PARTICLE_TYPES[particle_type]['class']\n        try:\n            particle_coords[k] = load_coordinate(experiment,particle_type)\n        except Exception as e:\n            print(f\"Error reading {particle_type} coordinates: {e}\")\n            particle_coords[k] = np.array([])\n    \n    return particle_coords\n\ndef load_all_cube_coordinates(experiment='TS_5_4',cube_size=cube_size):\n    \"\"\"Load coordinates for all particle types.\"\"\"\n    used_cube = []\n    \n    for particle_type in PARTICLE_TYPES.keys():\n        k = PARTICLE_TYPES[particle_type]['class']\n        used_cube += load_cube_coordinate(experiment,particle_type,cube_size)\n    \n    return used_cube","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def save_empty_cube(experiment, cube_key, cube_size, res):\n    cube_ = load_denoise_cube(experiment, cube_key, cube_idx_to_center, cube_size, res=res)\n    if cube_ is not None:\n        X = cube_\n        y = 5\n\n        cube_z, cube_y, cube_x = cube_key\n        filename = f\"{experiment}-empty-{cube_z}-{cube_y}-{cube_x}.npz\"\n        save_path = os.path.join(f'/kaggle/working/CryoET-{cube_size}', filename)\n\n        np.savez(save_path, X=X, y=y)\n\ndef generate_None_cube(experiment, used_cube, res='0'):\n    all_cubes = list(cube_idx_to_center.keys())\n    num_unused = len(used_cube) // 3\n\n    unused_cube_candidates = [cube for cube in all_cubes if cube not in used_cube]\n    unused_cubes = random.sample(unused_cube_candidates, num_unused)\n\n    Parallel(n_jobs=-1)(delayed(save_empty_cube)(experiment, cube_key, cube_size, res) for cube_key in unused_cubes)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def generate_cube_target_pair(experiments_list):\n    for experiment in tqdm(experiments_list):\n        used_cube = load_all_cube_coordinates(experiment)\n        generate_None_cube(experiment,used_cube)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Cut the 3D fig to many cubes, and each cube has one label\nX: cube np.array\n\ny: label","metadata":{}},{"cell_type":"code","source":"generate_cube_target_pair(train_experiments)","metadata":{"scrolled":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"all_cube_name = os.listdir('/kaggle/input/cryoet-cube/CryoET-(16, 32, 32)')\nprint(f\"Total files: {len(all_cube_name)}\")\ndef load_cube_data(file):\n    path_ = os.path.join('/kaggle/input/cryoet-cube/CryoET-(16, 32, 32)/',file)\n    if file.endswith('.npz'):\n        with np.load(path_) as data:\n            X = data['X']\n            y = data['y']\n    return (X,np.array([y]))","metadata":{"execution":{"iopub.status.busy":"2024-11-07T16:28:36.488631Z","iopub.execute_input":"2024-11-07T16:28:36.489036Z","iopub.status.idle":"2024-11-07T16:28:36.498540Z","shell.execute_reply.started":"2024-11-07T16:28:36.488999Z","shell.execute_reply":"2024-11-07T16:28:36.497208Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# how to load the data","metadata":{}},{"cell_type":"code","source":"X,y= load_cube_data(all_cube_name[0])","metadata":{"execution":{"iopub.status.busy":"2024-11-07T16:28:37.283839Z","iopub.execute_input":"2024-11-07T16:28:37.284845Z","iopub.status.idle":"2024-11-07T16:28:37.291389Z","shell.execute_reply.started":"2024-11-07T16:28:37.284795Z","shell.execute_reply":"2024-11-07T16:28:37.290305Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X.shape,y.shape","metadata":{"execution":{"iopub.status.busy":"2024-11-07T16:28:41.959769Z","iopub.execute_input":"2024-11-07T16:28:41.960210Z","iopub.status.idle":"2024-11-07T16:28:41.967669Z","shell.execute_reply.started":"2024-11-07T16:28:41.960169Z","shell.execute_reply":"2024-11-07T16:28:41.966312Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import collections\n\nparticle_counts = []\n\nfor exp_id in train_experiments:\n    all_coord = load_all_particle_coordinates(exp_id)\n    count = sum(len(coords) for coords in all_coord.values())  # Sum of all particles across categories\n    particle_counts.append(count)\n\n# Calculate the distribution of particle counts across all experiments\nparticle_distribution = collections.Counter(particle_counts)\n\n# Display the results\nprint(\"Particle count distribution across all experiments:\")\nfor count, freq in particle_distribution.items():\n    print(f\"Count: {count}, Frequency: {freq}\")\n\n# Convert particle_counts to numpy array for further analysis if needed\nparticle_counts_array = np.array(particle_counts)\n\n# Display summary statistics\nmean_count = np.mean(particle_counts_array)\nmedian_count = np.median(particle_counts_array)\nmax_count = np.max(particle_counts_array)\nmin_count = np.min(particle_counts_array)\n\nprint(\"\\nSummary Statistics:\")\nprint(f\"Mean count of particles: {mean_count}\")\nprint(f\"Median count of particles: {median_count}\")\nprint(f\"Max count of particles: {max_count}\")\nprint(f\"Min count of particles: {min_count}\")\n","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"coord = load_coordinate(train_experiments[1])\ntomogram = load_denoise(train_experiments[1])\ntomogram.shape\ndef visualize_(tomogram, coords, n_slices=3,particle='apo-ferritin', slice_thickness=10):\n    # Set number of columns per row to 3 and calculate the required number of rows\n    cols = 3\n    rows = (n_slices + cols - 1) // cols  # Ceiling division to determine rows\n\n    fig = plt.figure(figsize=(20, 8 * rows))  # Adjusted figure height to bring elements closer\n    grid = ImageGrid(fig, 111,\n                    nrows_ncols=(rows, cols),\n                    axes_pad=0.5,  # Increased padding between images\n                    share_all=True,\n                    cbar_location=\"right\",\n                    cbar_mode=\"single\",\n                    cbar_size=\"5%\",\n                    cbar_pad=0.1)\n    \n    # Normalize tomogram data\n    vmin, vmax = np.percentile(tomogram, (1, 99))\n    normalized_tomogram = np.clip((tomogram - vmin) / (vmax - vmin), 0, 1)\n    \n    # Calculate evenly spaced z-positions\n    z_positions = np.linspace(0, tomogram.shape[0] - 1, n_slices, dtype=int)\n    \n    # Plot each slice\n    for idx, ax in enumerate(grid):\n        if idx < n_slices:\n            z = z_positions[idx]\n            \n            # Show tomogram slice\n            im = ax.imshow(normalized_tomogram[z, :, :], vmin=0, vmax=1)\n            \n            # Find particles near this slice\n            mask = np.abs(coords[:, 0] - z) < slice_thickness\n            if np.any(mask):\n                style = PARTICLE_TYPES[particle]\n                ax.scatter(coords[mask, 2], coords[mask, 1],\n                         color=style['color'], marker=style['marker'],\n                         s=100, facecolors='none', linewidth=2,\n                         label=f\"{particle}\\n({style['difficulty']})\")\n            \n            ax.set_title(f'Slice Z={z}\\n({np.sum(mask)} particles visible)')\n            ax.grid(False)\n            \n            # Set the axes limits to match the tomogram dimensions\n            ax.set_xlim(0, tomogram.shape[2])\n            ax.set_ylim(tomogram.shape[1], 0)  # Inverted y-axis to match image coordinates\n        else:\n            ax.axis('off')  # Turn off any extra axes\n\n    # Add colorbar and title\n    grid.cbar_axes[0].colorbar(im)\n    \n    plt.suptitle('Apo-ferritin Particles in Tomogram Slices\\n' + \n                 f'Showing particles within ±{slice_thickness} units of each slice',\n                 fontsize=16, y=0.9)  # Adjusted y position of the title\n    \n    # Add legend to the first subplot\n    grid[0].legend(bbox_to_anchor=(1.5, 1.0))\n    \n    plt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"visualize_(tomogram,coord,n_slices=6)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def visualize_all_particles(tomogram, particle_coords, n_slices=3, slice_thickness=20):\n    \"\"\"\n    Visualize all particle types in tomogram slices with an overlay legend.\n    \"\"\"\n    # Set the number of columns per row to 3 and calculate the required number of rows\n    cols = 3\n    rows = (n_slices + cols - 1) // cols  # Ceiling division to determine rows\n\n    fig = plt.figure(figsize=(20, 8 * rows))  # Adjusted figure height to fit multiple rows\n    grid = ImageGrid(fig, 111,\n                     nrows_ncols=(rows, cols),\n                     axes_pad=1,  # Adjusted padding between images\n                     share_all=True,\n                     cbar_location=\"right\",\n                     cbar_mode=\"single\",\n                     cbar_size=\"5%\",\n                     cbar_pad=0.1)\n    \n    # Normalize tomogram data\n    vmin, vmax = np.percentile(tomogram, (1, 99))\n    normalized_tomogram = np.clip((tomogram - vmin) / (vmax - vmin), 0, 1)\n    \n    # Find z-positions with maximum particle density\n    all_z_coords = []\n    for coords in particle_coords.values():\n        if len(coords) > 0:\n            all_z_coords.extend(coords[:, 0])\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    # Plot each slice\n    for idx, ax in enumerate(grid):\n        if idx < n_slices:\n            z = int(z_positions[idx])\n            \n            # Show tomogram slice\n            im = ax.imshow(normalized_tomogram[z, :, :], vmin=0, vmax=1)\n            \n            # Plot each particle type\n            particles_in_slice = 0\n            particle_counts = {}\n            \n            for par_class, coords in particle_coords.items():\n                particle_type = class_particle[par_class]\n                if len(coords) > 0:\n                    # Find particles near this slice\n                    mask = np.abs(coords[:, 0] - z) < slice_thickness\n                    if np.any(mask):\n                        style = PARTICLE_TYPES[particle_type]\n                        ax.scatter(coords[mask, 2], coords[mask, 1],\n                                   color=style['color'], marker=style['marker'],\n                                   s=100, facecolors='none', linewidth=2,\n                                   label=f\"{particle_type} ({style['difficulty']})\")\n                        count = np.sum(mask)\n                        particles_in_slice += count\n                        particle_counts[particle_type] = count\n            \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=10)\n            \n            ax.grid(False)\n            \n            # Set the axes limits to match the tomogram dimensions\n            ax.set_xlim(0, tomogram.shape[2])\n            ax.set_ylim(tomogram.shape[1], 0)  # Inverted y-axis to match image coordinates\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_linewidth(2)\n        else:\n            ax.axis('off')  # Turn off any extra axes if n_slices < grid size\n\n    # Add colorbar and title\n    grid.cbar_axes[0].colorbar(im)\n    \n    plt.suptitle('All Particle Types in Tomogram Slices\\n' + \n                 f'Showing particles within ±{slice_thickness} units of each slice',\n                 fontsize=16,y=0.9)  # Adjusted y position of the title\n    \n    # Add legend with semi-transparent background to improve readability\n\n    \n    # Print overall particle statistics\n    print(\"\\nOverall Particle Statistics:\")\n    print(\"-\" * 50)\n    for particle_type, coords in particle_coords.items():\n        particle_type = class_particle[par_class]\n        if len(coords) > 0:\n            print(f\"\\n{particle_type} ({PARTICLE_TYPES[particle_type]['difficulty']}):\")\n            print(f\"Total particles: {len(coords)}\")\n            print(f\"Z range: {coords[:, 0].min():.1f} to {coords[:, 0].max():.1f}\")\n    \n    plt.show()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"visualize_all_particles(tomogram, all_coord,6,16)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}