Created
September 26, 2022 04:55
-
-
Save gerwang/d4d03eeea4a496ae5494ad075ac009d7 to your computer and use it in GitHub Desktop.
Obtain the transformation matrix from data in PSNeRF's space to the original DiLiGent-MV space
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "code", | |
"execution_count": 51, | |
"id": "980b811a-6958-4f73-be31-ea672b3c30c7", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import numpy as np\n", | |
"import json\n", | |
"from scipy import io as sio\n", | |
"import trimesh" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 45, | |
"id": "776f098c-776a-4354-afea-af039923e953", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import numpy\n", | |
"\n", | |
"\n", | |
"def affine_matrix_from_points(v0, v1, scale=True):\n", | |
" \"\"\"Return affine transform matrix to register two point sets.\n", | |
" v0 and v1 are shape (ndims, \\*) arrays of at least ndims non-homogeneous\n", | |
" coordinates, where ndims is the dimensionality of the coordinate space.\n", | |
" If shear is False, a similarity transformation matrix is returned.\n", | |
" If also scale is False, a rigid/Euclidean transformation matrix\n", | |
" is returned.\n", | |
" By default the algorithm by Hartley and Zissermann [15] is used.\n", | |
" If usesvd is True, similarity and Euclidean transformation matrices\n", | |
" are calculated by minimizing the weighted sum of squared deviations\n", | |
" (RMSD) according to the algorithm by Kabsch [8].\n", | |
" Otherwise, and if ndims is 3, the quaternion based algorithm by Horn [9]\n", | |
" is used, which is slower when using this Python implementation.\n", | |
" The returned matrix performs rotation, translation and uniform scaling\n", | |
" (if specified).\n", | |
" >>> numpy.set_printoptions(precision=5, suppress=True)\n", | |
" >>> v0 = [[0, 1031, 1031, 0], [0, 0, 1600, 1600]]\n", | |
" >>> v1 = [[675, 826, 826, 677], [55, 52, 281, 277]]\n", | |
" >>> affine_matrix_from_points(v0, v1)\n", | |
" array([[ 0.14549, 0.00062, 675.50008],\n", | |
" [ 0.00048, 0.14094, 53.24971],\n", | |
" [ 0. , 0. , 1. ]])\n", | |
" >>> T = translation_matrix(numpy.random.random(3)-0.5)\n", | |
" >>> R = random_rotation_matrix(numpy.random.random(3))\n", | |
" >>> S = scale_matrix(numpy.random.random())\n", | |
" >>> M = concatenate_matrices(T, R, S)\n", | |
" >>> v0 = (numpy.random.rand(4, 100) - 0.5) * 20\n", | |
" >>> v0[3] = 1\n", | |
" >>> v1 = numpy.dot(M, v0)\n", | |
" >>> v0[:3] += numpy.random.normal(0, 1e-8, 300).reshape(3, -1)\n", | |
" >>> M = affine_matrix_from_points(v0[:3], v1[:3])\n", | |
" >>> numpy.allclose(v1, numpy.dot(M, v0))\n", | |
" True\n", | |
" More examples in superimposition_matrix()\n", | |
" \"\"\"\n", | |
" v0 = numpy.array(v0, dtype=numpy.float64, copy=True)\n", | |
" v1 = numpy.array(v1, dtype=numpy.float64, copy=True)\n", | |
"\n", | |
" ndims = v0.shape[0]\n", | |
" if ndims < 2 or v0.shape[1] < ndims or v0.shape != v1.shape:\n", | |
" raise ValueError(\"input arrays are of wrong shape or type\")\n", | |
"\n", | |
" # move centroids to origin\n", | |
" t0 = -numpy.mean(v0, axis=1)\n", | |
" M0 = numpy.identity(ndims + 1)\n", | |
" M0[:ndims, ndims] = t0\n", | |
" v0 += t0.reshape(ndims, 1)\n", | |
" t1 = -numpy.mean(v1, axis=1)\n", | |
" M1 = numpy.identity(ndims + 1)\n", | |
" M1[:ndims, ndims] = t1\n", | |
" v1 += t1.reshape(ndims, 1)\n", | |
"\n", | |
" # Rigid transformation via SVD of covariance matrix\n", | |
" u, s, vh = numpy.linalg.svd(numpy.dot(v1, v0.T))\n", | |
" # rotation matrix from SVD orthonormal bases\n", | |
" R = numpy.dot(u, vh)\n", | |
" if numpy.linalg.det(R) < 0.0:\n", | |
" # R does not constitute right handed system\n", | |
" R -= numpy.outer(u[:, ndims - 1], vh[ndims - 1, :] * 2.0)\n", | |
" s[-1] *= -1.0\n", | |
" # homogeneous transformation matrix\n", | |
" M = numpy.identity(ndims + 1)\n", | |
" M[:ndims, :ndims] = R\n", | |
"\n", | |
" if scale:\n", | |
" # Affine transformation; scale is ratio of RMS deviations from centroid\n", | |
" v0 *= v0\n", | |
" v1 *= v1\n", | |
" M[:ndims, :ndims] *= numpy.sqrt(numpy.sum(v1) / numpy.sum(v0))\n", | |
"\n", | |
" # move centroids back\n", | |
" M = numpy.dot(numpy.linalg.inv(M1), numpy.dot(M, M0))\n", | |
" M /= M[ndims, ndims]\n", | |
" return M" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 2, | |
"id": "5036a472-0f42-4804-9d73-d955ca3d9cca", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"json_path = '/path/to/psnerf/data/bear/params.json'\n", | |
"mat_path = '/path/to/DiLiGenT-MV/mvpmsData/bearPNG/Calib_Results.mat'" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 19, | |
"id": "26e469e2-b0ec-40ef-b564-6536fdc1420c", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"np.set_printoptions(precision = 3)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"id": "e60c07d7-45f7-4d78-9280-b05626c2f3e1", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"json_dict = json.load(open(json_path))" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"id": "2809907b-cd4b-42b8-995a-f3f75a237e63", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"dict_keys(['obj_name', 'n_view', 'view_train', 'view_test', 'K', 'pose_c2w', 'light_is_same', 'light_direction', 'gt_normal_world', 'imhw', 'light_intensity'])" | |
] | |
}, | |
"execution_count": 5, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"json_dict.keys()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 8, | |
"id": "707bba04-4c14-477f-92dd-bf4eb00626ac", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"mat_dict = sio.loadmat(mat_path)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"id": "ce158145-5d19-4a69-a31d-6d3f17932cb4", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"dict_keys(['__header__', '__version__', '__globals__', 'KK', 'Rc_1', 'Tc_1', 'Rc_2', 'Tc_2', 'Rc_3', 'Tc_3', 'Rc_4', 'Tc_4', 'Rc_5', 'Tc_5', 'Rc_6', 'Tc_6', 'Rc_7', 'Tc_7', 'Rc_8', 'Tc_8', 'Rc_9', 'Tc_9', 'Rc_10', 'Tc_10', 'Rc_11', 'Tc_11', 'Rc_12', 'Tc_12', 'Rc_13', 'Tc_13', 'Rc_14', 'Tc_14', 'Rc_15', 'Tc_15', 'Rc_16', 'Tc_16', 'Rc_17', 'Tc_17', 'Rc_18', 'Tc_18', 'Rc_19', 'Tc_19', 'Rc_20', 'Tc_20'])" | |
] | |
}, | |
"execution_count": 10, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"mat_dict.keys()" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 25, | |
"id": "29542962-2150-4224-b44d-b85803b83071", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([[ 7.881e-03, -4.447e-01, 8.957e-01],\n", | |
" [ 1.000e+00, 8.589e-05, -8.756e-03],\n", | |
" [ 3.817e-03, 8.957e-01, 4.447e-01]])" | |
] | |
}, | |
"execution_count": 25, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"np.linalg.inv(mat_dict['Rc_1']) @ np.array([[1, 0, 0], [0, -1, 0], [0, 0, -1]])" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 28, | |
"id": "70405ac0-2526-442e-8d42-5d45dfa1a1b6", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"tmp = np.vstack([np.hstack([mat_dict['Rc_1'], mat_dict['Tc_1']]), np.array([[0, 0, 0, 1]])])" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 30, | |
"id": "a8f39367-6a9b-476b-b85d-c5df829c4080", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"tmp2 = np.linalg.inv(tmp) @ np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 31, | |
"id": "b569fc10-298b-4794-9aaa-6231c6430b03", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([[ 7.881e-03, -4.447e-01, 8.957e-01, 1.448e+03],\n", | |
" [ 1.000e+00, 8.589e-05, -8.756e-03, 7.055e+01],\n", | |
" [ 3.817e-03, 8.957e-01, 4.447e-01, 7.102e+02],\n", | |
" [ 0.000e+00, 0.000e+00, 0.000e+00, 1.000e+00]])" | |
] | |
}, | |
"execution_count": 31, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"tmp2" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"id": "3affc08b-4b19-46bc-acae-82e71f51bf11", | |
"metadata": {}, | |
"source": [ | |
"The matlab file stores w2c camera, in OpenGL format; Whilist the json file stores c2w camera, in OpenCV format." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 26, | |
"id": "50052a3a-75df-4af3-8adb-e74f530c6485", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"(3, 1)" | |
] | |
}, | |
"execution_count": 26, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"mat_dict['Tc_1'].shape" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 15, | |
"id": "5c1d94dd-870a-47be-b434-e5b81fe3df4d", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"20" | |
] | |
}, | |
"execution_count": 15, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"len(json_dict['pose_c2w'])" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 22, | |
"id": "4320fada-c726-4809-bce0-0168d6719d41", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([[ 7.881e-03, -4.447e-01, 8.957e-01, 2.785e+01],\n", | |
" [ 1.000e+00, 8.589e-05, -8.756e-03, -2.439e-01],\n", | |
" [ 3.817e-03, 8.957e-01, 4.447e-01, 1.349e+01],\n", | |
" [ 0.000e+00, 0.000e+00, 0.000e+00, 1.000e+00]])" | |
] | |
}, | |
"execution_count": 22, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"np.array(json_dict['pose_c2w'][0])" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 34, | |
"id": "993c73cb-7314-4ac2-8e38-bd380c33f282", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([[ 27.847, -0.244, 13.494],\n", | |
" [ 26.223, -9.362, 13.532],\n", | |
" [ 21.921, -17.231, 13.562],\n", | |
" [ 15.725, -23.098, 13.576],\n", | |
" [ 7.178, -27.083, 13.564],\n", | |
" [ -0.931, -28.077, 13.528],\n", | |
" [ -8.471, -26.855, 13.47 ],\n", | |
" [-16.238, -23.109, 13.386],\n", | |
" [-22.47 , -17.205, 13.362],\n", | |
" [-26.97 , -8.763, 13.357],\n", | |
" [-28.39 , 0.574, 13.383],\n", | |
" [-27.115, 8.452, 13.427],\n", | |
" [-22.69 , 17.066, 13.47 ],\n", | |
" [-16.392, 23.147, 13.498],\n", | |
" [ -8.09 , 27.122, 13.532],\n", | |
" [ 1.003, 28.198, 13.542],\n", | |
" [ 9.681, 26.405, 13.519],\n", | |
" [ 18.231, 21.274, 13.477],\n", | |
" [ 23.4 , 15.265, 13.466],\n", | |
" [ 26.894, 7.358, 13.474]])" | |
] | |
}, | |
"execution_count": 34, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"json_campos = []\n", | |
"for x in json_dict['pose_c2w']:\n", | |
" json_campos.append(np.array(x)[:3, 3])\n", | |
"json_campos = np.stack(json_campos)\n", | |
"json_campos" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 35, | |
"id": "7541201a-9d6f-44f8-9561-7ab43fc166a9", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([[ 1448.494, 70.547, 710.202],\n", | |
" [ 1368.949, -376.224, 712.081],\n", | |
" [ 1158.146, -761.802, 713.549],\n", | |
" [ 854.537, -1049.326, 714.233],\n", | |
" [ 435.712, -1244.561, 713.633],\n", | |
" [ 38.388, -1293.29 , 711.86 ],\n", | |
" [ -331.093, -1233.389, 709.028],\n", | |
" [ -711.67 , -1049.831, 704.908],\n", | |
" [-1017.024, -760.565, 703.748],\n", | |
" [-1237.516, -346.883, 703.499],\n", | |
" [-1307.091, 110.638, 704.75 ],\n", | |
" [-1244.64 , 496.654, 706.914],\n", | |
" [-1027.799, 918.743, 709.024],\n", | |
" [ -719.232, 1216.712, 710.392],\n", | |
" [ -312.386, 1411.464, 712.058],\n", | |
" [ 133.17 , 1464.208, 712.541],\n", | |
" [ 558.389, 1376.324, 711.452],\n", | |
" [ 977.305, 1124.932, 709.374],\n", | |
" [ 1230.589, 830.503, 708.837],\n", | |
" [ 1401.82 , 443.062, 709.218]])" | |
] | |
}, | |
"execution_count": 35, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"mat_campos = []\n", | |
"for i in range(len(json_dict['pose_c2w'])):\n", | |
" tmp = np.vstack([np.hstack([mat_dict[f'Rc_{i + 1}'], mat_dict[f'Tc_{i + 1}']]), np.array([[0, 0, 0, 1]])])\n", | |
" tmp = np.linalg.inv(tmp) @ np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])\n", | |
" mat_campos.append(tmp[:3, 3])\n", | |
"mat_campos = np.stack(mat_campos)\n", | |
"mat_campos" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 40, | |
"id": "d32cd8a0-411d-438c-b97e-bd52710a10b7", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([[ 4.900e+01, 5.039e-08, 2.348e-07, 8.400e+01],\n", | |
" [-5.039e-08, 4.900e+01, 2.363e-07, 8.250e+01],\n", | |
" [-2.348e-07, -2.363e-07, 4.900e+01, 4.900e+01],\n", | |
" [ 0.000e+00, 0.000e+00, 0.000e+00, 1.000e+00]])" | |
] | |
}, | |
"execution_count": 40, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"M = affine_matrix_from_points(json_campos.T, mat_campos.T)\n", | |
"M" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 43, | |
"id": "2cc3cb14-b7aa-4ccc-875d-ca0cef2c2c14", | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"array([[ 1448.494, 70.547, 710.202],\n", | |
" [ 1368.949, -376.224, 712.081],\n", | |
" [ 1158.146, -761.802, 713.549],\n", | |
" [ 854.537, -1049.326, 714.233],\n", | |
" [ 435.712, -1244.561, 713.633],\n", | |
" [ 38.388, -1293.29 , 711.86 ],\n", | |
" [ -331.093, -1233.389, 709.028],\n", | |
" [ -711.67 , -1049.831, 704.908],\n", | |
" [-1017.024, -760.565, 703.748],\n", | |
" [-1237.516, -346.883, 703.499],\n", | |
" [-1307.091, 110.638, 704.751],\n", | |
" [-1244.64 , 496.654, 706.914],\n", | |
" [-1027.799, 918.743, 709.024],\n", | |
" [ -719.232, 1216.712, 710.392],\n", | |
" [ -312.386, 1411.464, 712.058],\n", | |
" [ 133.17 , 1464.208, 712.541],\n", | |
" [ 558.389, 1376.324, 711.452],\n", | |
" [ 977.305, 1124.932, 709.374],\n", | |
" [ 1230.589, 830.503, 708.837],\n", | |
" [ 1401.82 , 443.062, 709.218]])" | |
] | |
}, | |
"execution_count": 43, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"(M[:3, :3] @ json_campos.T + M[:3, 3:]).T" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 52, | |
"id": "e9a8a8bc-8ba0-4ef7-b289-c36ba14f85a8", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"mesh = trimesh.load('/path/to/psnerf/mesh/bear.ply')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 53, | |
"id": "4048677f-86b4-4236-bb77-4ff1e83b817b", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"mesh.vertices = (M[:3, :3] @ mesh.vertices.T + M[:3, 3:]).T" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 55, | |
"id": "d42c2eee-ae25-40cf-97db-b8f03fffd554", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"mesh.export('/path/to/psnerf/mesh/bear_world.ply');" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 46, | |
"id": "98b82a99-9949-4fa9-9426-8dc9a7d0ca69", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"obj_names = ['bear', 'buddha', 'cow', 'pot2', 'reading']" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 47, | |
"id": "40de7cd1-9ebd-49a5-867d-ac7d65b23e82", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"json_template_path = '/path/to/psnerf/data/{}/params.json'\n", | |
"mat_template_path = '/path/to/DiLiGenT-MV/mvpmsData/{}PNG/Calib_Results.mat'\n", | |
"output_template_path = '/path/to/psnerf/data/{}/scale_mat.npy'" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 49, | |
"id": "5a340ef5-a323-4e5e-b08f-4cfa4c81b1e0", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def process(obj_name):\n", | |
" json_path = json_template_path.format(obj_name)\n", | |
" mat_path = mat_template_path.format(obj_name)\n", | |
" output_path = output_template_path.format(obj_name)\n", | |
" \n", | |
" json_dict = json.load(open(json_path))\n", | |
" json_campos = []\n", | |
" for x in json_dict['pose_c2w']:\n", | |
" json_campos.append(np.array(x)[:3, 3])\n", | |
" json_campos = np.stack(json_campos)\n", | |
" \n", | |
" mat_dict = sio.loadmat(mat_path)\n", | |
" mat_campos = []\n", | |
" for i in range(len(json_dict['pose_c2w'])):\n", | |
" tmp = np.vstack([np.hstack([mat_dict[f'Rc_{i + 1}'], mat_dict[f'Tc_{i + 1}']]), np.array([[0, 0, 0, 1]])])\n", | |
" tmp = np.linalg.inv(tmp) @ np.array([[1, 0, 0, 0], [0, -1, 0, 0], [0, 0, -1, 0], [0, 0, 0, 1]])\n", | |
" mat_campos.append(tmp[:3, 3])\n", | |
" mat_campos = np.stack(mat_campos)\n", | |
" M = affine_matrix_from_points(json_campos.T, mat_campos.T)\n", | |
" np.save(output_path, M)" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 50, | |
"id": "3c19b781-91cf-4a61-a797-808ed9237e11", | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"for obj_name in obj_names:\n", | |
" process(obj_name)" | |
] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"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.8.8" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 5 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment