Commit f8eb5465 authored by Benedikt Zoennchen's avatar Benedikt Zoennchen

update TrajectoryMetric-notebook: computation of the velocity, densities and...

update TrajectoryMetric-notebook: computation of the velocity, densities and the fundamental diagram. Cutting of trajectories.
parent 73604bc0
......@@ -11,8 +11,11 @@
"import numpy as np\n",
"import pandas as pd\n",
"import math\n",
"import matplotlib as mpl\n",
"import matplotlib.pyplot as plt\n",
"from matplotlib.lines import Line2D\n",
"import seaborn as sns\n",
"sns.set(style=\"ticks\")\n",
"\n",
"from IPython.core.display import display, HTML\n",
"display(HTML('<style>.container { width:100% !important; }</style>'))"
......@@ -43,6 +46,8 @@
" for pedId in trajectories:\n",
" trajectory_append(pedId, trajectories[pedId], llist)\n",
" dataframe = pd.DataFrame(llist, columns=['pedestrianId','startX','startY','startTime','endX','endY','endTime'])\n",
" dataframe[\"distance\"] = np.sqrt(np.square(dataframe[\"endX\"] - dataframe[\"startX\"]) + np.square(dataframe[\"endY\"] - dataframe[\"startY\"]))\n",
" dataframe[\"velocity\"] = dataframe[\"distance\"] / (dataframe[\"endTime\"] - dataframe[\"startTime\"])\n",
" return dataframe"
]
},
......@@ -82,7 +87,6 @@
"source": [
"def load_experiment(file):\n",
" fps = 16\n",
" pad = pd.DataFrame([[np.nan, np.nan, np.nan, np.nan, np.nan]], columns=['pedestrianId', 'timeStep', 'x', 'y', 'e'])\n",
" data = pd.read_csv(\n",
" file, \n",
" sep=' ', \n",
......@@ -91,16 +95,33 @@
" header=None, \n",
" skiprows=0)\n",
" \n",
" cc = pd.concat([pad, data], ignore_index=True)\n",
" rows = []\n",
" #print(trajectories)\n",
" last_ped_id = None\n",
" lastX = None\n",
" lastY = None\n",
" for row in data.itertuples():\n",
" endX = row.x / 100 + 18.7\n",
" endY = row.y / 100 + 4.2\n",
" startTime = row.timeStep / fps - 1/fps\n",
" endTime = row.timeStep / fps\n",
" if last_ped_id is None or last_ped_id != row.pedestrianId:\n",
" startX = np.nan\n",
" startY = np.nan\n",
" distance = np.nan\n",
" velocity = np.nan\n",
" else:\n",
" startX = lastX / 100 + 18.7\n",
" startY = lastY / 100 + 4.2\n",
" distance = np.sqrt(np.square(endX - startX) + np.square(endY - startY))\n",
" velocity = distance / (endTime - startTime)\n",
" last_ped_id = row.pedestrianId\n",
" lastX = row.x\n",
" lastY = row.y\n",
" \n",
" data['endX'] = data['x'] / 100 + 18.7\n",
" data['endY'] = data['y'] / 100 + 4.2\n",
" data['startX'] = cc['x'] / 100 + 18.7\n",
" data['startY'] = cc['y'] / 100 + 4.2\n",
" data['startTime'] = data['timeStep'] / fps - 1/fps\n",
" data['endTime'] = data['timeStep'] / fps\n",
" data = data.drop(columns=['timeStep','x','y','e'])\n",
" return data\n",
" rows.append([row.pedestrianId, startX, startY, endX, endY, startTime, endTime, distance, velocity])\n",
" dataframe = pd.DataFrame(rows, columns=['pedestrianId', 'startX', 'startY', 'endX', 'endY','startTime','endTime','distance','velocity'])\n",
" return dataframe\n",
" \n",
"def to_trajectories(data):\n",
" trajectories = dict({})\n",
......@@ -205,11 +226,33 @@
"trajectoriesReal[\"timeDelta\"] = trajectoriesReal[\"endTime\"] - trajectoriesReal[\"startTime\"]\n",
"evacuation_time = trajectoriesReal.groupby([\"pedestrianId\"])[\"timeDelta\"].sum()\n",
"\n",
"# trajectories starting from left\n",
"cut_minX = trajectoriesReal[trajectoriesReal[\"endX\"] < 15].groupby([\"pedestrianId\"])[\"endX\"].min().max()\n",
"\n",
"# trajectories starting from right\n",
"cut_maxX = trajectoriesReal[trajectoriesReal[\"endX\"] > 21].groupby([\"pedestrianId\"])[\"endX\"].max().min()\n",
"cut_maxY = trajectoriesReal.groupby([\"pedestrianId\"])[\"endY\"].max().min()\n",
"\n",
"print(\"Evacuation time (real data)\")\n",
"print(\"- mean: {:.2f} [s]\".format(evacuation_time.mean()))\n",
"print(\"- std: {:.2f} [s]\".format(evacuation_time.std()))\n",
"print(\"- min: {:.2f} [s]\".format(evacuation_time.min()))\n",
"print(\"- max: {:.2f} [s]\".format(evacuation_time.max()))"
"print(\"- max: {:.2f} [s]\".format(evacuation_time.max()))\n",
"print(\"- minX: {:.2f} [m]\".format(cut_minX))\n",
"print(\"- maxX: {:.2f} [m]\".format(cut_maxX))\n",
"print(\"- maxY: {:.2f} [m]\".format(cut_maxY))"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"rightX = trajectoriesReal[trajectoriesReal.endX < 16].groupby([\"pedestrianId\"])[\"endX\"].min().max()\n",
"leftX = trajectoriesReal[trajectoriesReal.endX > 16].groupby([\"pedestrianId\"])[\"endX\"].max().min()\n",
"topY = trajectoriesReal.groupby([\"pedestrianId\"])[\"endY\"].max().min()\n",
"topY"
]
},
{
......@@ -244,9 +287,15 @@
" query = 'pedestrianId == ' + str(pedId)\n",
" return trajectories.query(query)\n",
"\n",
"def get_trajectories(t, trajectories):\n",
" return trajectories[np.logical_and(trajectories.startTime <= t, trajectories.endTime >= t)]\n",
"\n",
"def get_pedestrianIds(trajectories):\n",
" return trajectories['pedestrianId'].unique()\n",
"\n",
"#def get_velocity(trajectories, t, dt):\n",
"# trajectories[np.logical_and(trajectory.endX >= xmax, trajectory.startX < xmax)]\n",
"\n",
"def get_footstep(trajectory, i):\n",
" \"\"\"returns the i-ths footstep.\"\"\"\n",
" return trajectory.iloc[i];\n",
......@@ -287,7 +336,96 @@
"\n",
"def cut_soft(trajectory, sTime, eTime):\n",
" query = 'endTime > ' + str(sTime) + ' and startTime < ' + str(eTime)\n",
" return trajectory.query(query)"
" return trajectory.query(query)\n",
"\n",
"def cuthead_trajectory_by(trajectory, ymin, ymax):\n",
" i1 = trajectory[trajectory.endY >= ymax].index.min()\n",
" i2 = trajectory[trajectory.endY <= ymin].index.min()\n",
" #assert (i1 is np.nan and i2 is not np.nan) or (i1 is not np.nan and i2 is np.nan)\n",
" y = ymax if i2 is np.nan or (i1 is not np.nan and i1 < i2) else ymin\n",
" i = i1 if y == ymax else i2\n",
" #print(i)\n",
" # cut the footstep at the tail to exactly fit xmin or xmax\n",
" fs = trajectory.loc[i]\n",
" start = np.array([fs[\"startX\"], fs[\"startY\"]])\n",
" end = np.array([fs[\"endX\"], fs[\"endY\"]])\n",
" endTime = fs[\"endTime\"]\n",
" startTime = fs[\"startTime\"]\n",
" distance = fs[\"distance\"]\n",
" velocity = fs[\"velocity\"]\n",
" d = end - start\n",
" if abs(fs[\"endY\"] - fs[\"startY\"]) > 0.00001:\n",
" r = (y - fs[\"startY\"]) / (fs[\"endY\"] - fs[\"startY\"])\n",
" end = start + (d * r)\n",
" time = fs[\"endTime\"] - fs[\"startTime\"]\n",
" endTime = fs[\"startTime\"] + (time * r)\n",
" distance = np.linalg.norm(end - start)\n",
" velocity = distance / (endTime - startTime)\n",
" \n",
" df = trajectory.loc[:i-1]\n",
" llist = [[fs[\"pedestrianId\"],fs[\"startX\"],fs[\"startY\"],fs[\"startTime\"],end[0],end[1],endTime,distance,velocity]]\n",
" df_head = pd.DataFrame(llist, columns=['pedestrianId','startX','startY','startTime','endX','endY','endTime','distance','velocity'])\n",
" df = df.append(df_head, ignore_index=True)\n",
" return df\n",
"\n",
"def cuttail_trajectory_by(trajectory, xmin, xmax):\n",
" #i1 = trajectory[np.logical_and(trajectory.endX >= xmax, trajectory.startX < xmax)].index.max()\n",
" i1 = trajectory[trajectory.endX >= xmax].index.max()\n",
" i2 = trajectory[trajectory.endX <= xmin].index.max()\n",
" #assert (i1 is np.nan and i2 is not np.nan) or (i1 is not np.nan and i2 is np.nan)\n",
" x = xmax if i2 is np.nan or (i1 is not np.nan and i1 > i2) else xmin\n",
" i = i1 if x == xmax else i2\n",
" i = i+1\n",
" # cut the footstep at the tail to exactly fit xmin or xmax\n",
" fs = trajectory.loc[i]\n",
" start = np.array([fs[\"startX\"], fs[\"startY\"]])\n",
" end = np.array([fs[\"endX\"], fs[\"endY\"]])\n",
" startTime = fs[\"startTime\"]\n",
" endTime = fs[\"endTime\"]\n",
" distance = fs[\"distance\"]\n",
" velocity = fs[\"velocity\"]\n",
" d = end - start\n",
" if abs(fs[\"endX\"] - fs[\"startX\"]) > 0.00001:\n",
" r = (x - fs[\"startX\"]) / (fs[\"endX\"] - fs[\"startX\"])\n",
" end = start + (d * r)\n",
" time = fs[\"endTime\"] - fs[\"startTime\"]\n",
" endTime = fs[\"startTime\"] + (time * r)\n",
" distance = np.linalg.norm(end - start)\n",
" velocity = distance / (endTime - startTime)\n",
" \n",
" df = trajectory.loc[i+1:]\n",
" llist = [[fs[\"pedestrianId\"],fs[\"startX\"],fs[\"startY\"],fs[\"startTime\"],end[0],end[1],endTime,distance,velocity]]\n",
" df_tail = pd.DataFrame(llist, columns=['pedestrianId','startX','startY','startTime','endX','endY','endTime','distance','velocity'])\n",
" df_tail = df_tail.append(df, ignore_index=True)\n",
" return df_tail\n",
"\n",
"def cuthead_by(trajectories, ymin, ymax):\n",
" df = pd.DataFrame([], columns=['pedestrianId','startX','startY','startTime','endX','endY','endTime'])\n",
" pedIds = get_pedestrianIds(trajectories)\n",
" for pedId in pedIds:\n",
" df = df.append(cuthead_trajectory_by(get_trajectory(pedId, trajectories), ymin, ymax), ignore_index=True)\n",
" return df\n",
"\n",
"def cuttail_by(trajectories, xmin, xmax):\n",
" df = pd.DataFrame([], columns=['pedestrianId','startX','startY','startTime','endX','endY','endTime'])\n",
" pedIds = get_pedestrianIds(trajectories)\n",
" for pedId in pedIds:\n",
" df = df.append(cuttail_trajectory_by(get_trajectory(pedId, trajectories), xmin, xmax), ignore_index=True)\n",
" return df\n",
"\n",
"def cut(trajectories):\n",
" df = cuttail_by(trajectories, cut_minX, cut_maxX)\n",
" df = cuthead_by(df, -1000, cut_maxY)\n",
" return df\n",
"\n",
"traj = get_trajectory(2, trajectoriesReal)\n",
"ts = traj[traj.endX > 22].index.max()\n",
"ts\n",
"#cut(trajectoriesReal)\n",
"#cuttail_by(trajectoriesReal, 13, 22).head()\n",
"#cuthead_by(trajectoriesReal, 0, 4).tail()\n",
"#traj.loc[100]\n",
"#trajectoriesReal.tail()"
]
},
{
......@@ -313,6 +451,9 @@
" dy = y1-y2;\n",
" return np.sqrt(dx*dx + dy*dy);\n",
"\n",
"def mean_velocity_at(t, trajectories):\n",
" return get_trajectories(t, trajectories)['velocity'].mean()\n",
"\n",
"def trajectory_length(trajectory):\n",
" \"\"\"Euclidean length of a trajectory.\"\"\"\n",
" dx = trajectory['startX']-trajectory['endX']\n",
......@@ -458,7 +599,6 @@
"#foot_step_length(get_footstep(get_trajectory(1, ptrajectories), 0))\n",
"#trajectory_length(get_trajectory(1, ptrajectories))\n",
"#trajectory_speed(get_trajectory(1, ptrajectories))\n",
"cutTraj = cut(get_trajectory(1, ptrajectories), 0.0, 10.0)[['startTime', 'endTime']]\n",
"#cutTraj.mask(cutTraj['startTime'] <= 4 and 4 > cutTraj['endTime'])\n",
"#start_time(get_trajectory(1, ptrajectories))\n",
"#trajectories_position(ptrajectories, [1,2,3,4]).head()\n",
......@@ -468,7 +608,11 @@
"trajPos2 = trajPos2[~np.isnan(trajPos2.x)]\n",
"euclid_path_length(trajPos1, trajPos2)\n",
"euclid_len(ptrajectories,0,10000)\n",
"#print(total_inter_agent(ptrajectories, ptrajectories, [1,2]))"
"#print(total_inter_agent(ptrajectories, ptrajectories, [1,2]))\n",
"t = 0.5\n",
"ttraj = ptrajectories[np.logical_and(ptrajectories.startTime <= t, ptrajectories.endTime >= t)]\n",
"#ptrajectories[\"velocity\"] = numpy.linalg.norm(\n",
"get_trajectories(0.5, ptrajectories).head()"
]
},
{
......@@ -506,7 +650,26 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"# Plot trajectories"
"Here we cut all trajectory data such each left trajectory starts at the same $x$-coordinate and each right trajectory starts at the same $x$-coordinate. In addition each trajectory ends a the same $y$-coordinate."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"c_real_trajectories = cut(trajectoriesReal)\n",
"c_sim_trajecotories = cut(ptrajectories)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Plot trajectories\n",
"\n",
"Now we plot the cut trajectories."
]
},
{
......@@ -517,13 +680,14 @@
"source": [
"def to_line(trajectory, xleft):\n",
" \"\"\"Transforms a trajectory into a Line2D.\"\"\"\n",
" current_palette = sns.color_palette()\n",
" x = trajectory['endX'].values\n",
" y = trajectory['endY'].values\n",
" if x[0] < xleft:\n",
" c = current_palette[2]\n",
" else:\n",
" c = current_palette[0]\n",
" return x, y, Line2D(x, y, color=c, linewidth=0.2)\n",
" return x, y, Line2D(x, y, color=c, linewidth=0.3)\n",
"\n",
"def add_lines(trajectories, xleft, ax):\n",
" grouped = trajectories.groupby(['pedestrianId'])\n",
......@@ -538,60 +702,83 @@
"metadata": {},
"outputs": [],
"source": [
"import seaborn as sns\n",
"sns.set(style=\"ticks\")\n",
"\n",
"current_palette = sns.color_palette()\n",
"\n",
"x_vcenter = 17.5\n",
"y_vcenter = 5.2\n",
"\n",
"fig1 = plt.figure(figsize=(10,10))\n",
"ax1 = fig1.add_subplot(111)\n",
"add_lines(trajectoriesReal, 14, ax1)\n",
"ax1.set_xlim(x_vcenter-5, x_vcenter+5)\n",
"ax1.set_ylim(y_vcenter-4, y_vcenter+4)\n",
"ax1.set_aspect(1)\n",
"fig_trajectories = plt.figure(figsize=(10,10))\n",
"ax1_trajectories = fig_trajectories.add_subplot(121)\n",
"add_lines(c_real_trajectories, 16, ax1_trajectories)\n",
"ax1_trajectories.set_xlim(x_vcenter-5, x_vcenter+6)\n",
"ax1_trajectories.set_ylim(y_vcenter-4, y_vcenter+4)\n",
"ax1_trajectories.set_aspect(1)\n",
"\n",
"fig2 = plt.figure(figsize=(10,10))\n",
"ax2 = fig2.add_subplot(111)\n",
"add_lines(ptrajectories, 14, ax2)\n",
"ax2.set_xlim(x_vcenter-5, x_vcenter+5)\n",
"ax2.set_ylim(y_vcenter-4, y_vcenter+4)\n",
"ax2.set_aspect(1)\n",
"ax2_trajectories = fig_trajectories.add_subplot(122, sharey=ax1)\n",
"add_lines(c_sim_trajecotories, 16, ax2_trajectories)\n",
"plt.setp(ax2_trajectories.get_yticklabels(), visible=False)\n",
"ax2_trajectories.set_xlim(x_vcenter-5, x_vcenter+6)\n",
"ax2_trajectories.set_ylim(y_vcenter-4, y_vcenter+4)\n",
"ax2_trajectories.set_aspect(1)\n",
"\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"cell_type": "markdown",
"metadata": {},
"outputs": [],
"source": [
"times = np.arange(0,80,2)\n",
"y = list(map(lambda t: inter_agent_d(trajectories, t), times))\n",
"plt.plot(times, y, 'o')"
"# Plot velocities\n",
"\n",
"The following code plots the mean (over all agents / pedestrians) velocity at $t = 0, 0.5, \\ldots 70$ and the corresponding standard deviation. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"metadata": {
"scrolled": true
},
"outputs": [],
"source": [
"start_time(trajectories[1])\n",
"print(max_start_time(trajectories))\n",
"print(min_end_time(trajectories))"
"times = np.arange(0, 70, 0.2)\n",
"velocity1 = list(map(lambda t: mean_velocity_at(t, c_real_trajectories), times))\n",
"std1 = list(map(lambda t: get_trajectories(t, c_real_trajectories)['velocity'].std(),times))\n",
"\n",
"velocity2 = list(map(lambda t: mean_velocity_at(t, c_sim_trajecotories), times))\n",
"std2 = list(map(lambda t: get_trajectories(t, c_sim_trajecotories)['velocity'].std(),times))\n",
"\n",
"#df1 = pd.DataFrame({'velocity':vel1, 'time':times})\n",
"#df2 = pd.DataFrame({'velocity':vel2, 'time':times})\n",
"\n",
"fig_velocities = plt.figure(figsize=(10,5))\n",
"ax1_velocities = fig_velocities.add_subplot(121)\n",
"ax1_velocities.set_xlim(min(times),max(times))\n",
"ax1_velocities.set_ylim(0,2)\n",
"ax1_velocities.set_xlabel(\"time\")\n",
"ax1_velocities.set_ylabel(\"velocity\")\n",
"plt.errorbar(times, velocity1, std1, ecolor=sns.color_palette()[2])\n",
"ax2_velocities = fig_velocities.add_subplot(122)\n",
"ax2_velocities.set_xlim(min(times),max(times))\n",
"ax2_velocities.set_ylim(0,2)\n",
"ax2_velocities.set_xlabel(\"time\")\n",
"ax2_velocities.set_ylabel(\"velocity\")\n",
"plt.errorbar(times, velocity2, std2, ecolor=sns.color_palette()[2])\n",
"plt.show()\n",
"#ax6 = fig2.add_subplot(212)\n",
"#sns.relplot(x=\"startTime\", y=\"velocity\", kind=\"line\", ci=\"sd\", data=c_real_trajectories, ax=ax) #linewidth=0.5\n",
"#sns.relplot(x=\"startTime\", y=\"velocity\", kind=\"line\", ci=\"sd\", data=c_sim_trajecotories, ax=ax6)\n",
"#fmri = sns.load_dataset(\"fmri\")\n",
"#fmri\n",
"#c_real_trajectories[c_real_trajectories.endTime == 5.0]\n",
"#c_real_trajectories"
]
},
{
"cell_type": "code",
"execution_count": null,
"cell_type": "markdown",
"metadata": {},
"outputs": [],
"source": [
"print(position(map(lambda traj: traj[\"startTime\"], trajectories)[1], 0))"
"# Plot densities\n",
"\n",
"The following code plots the density inside the measurement area at $t = 0, 0.5, \\ldots 70$ and the corresponding standard deviation."
]
},
{
......@@ -600,7 +787,54 @@
"metadata": {},
"outputs": [],
"source": [
"pd.DataFrame([[1,2,3,4,5],[6,7,8,9,10]], columns=['pedestrianId','timeStep','x','y','time'])"
"def contains(x,y,rect):\n",
" #ma = mpl.patches.Rectangle((16.3,6.0), 2.4, 2.0)\n",
" return x >= rect.get_x() and y >= rect.get_y() and x <= rect.get_x() + rect.get_width() and y <= rect.get_y() + rect.get_height()\n",
"\n",
"def filter_by_time_and_place(t, rect, trajectories):\n",
" \"\"\"returns a subset of trajectories i.e. at most one footstep for each pedestrian / agent such that the footstep the position (x,y) is the position of the\n",
" agent at the time t contained in the rectanlge rect. Two new colums will be added for x and y.\"\"\"\n",
" traj = get_trajectories(t, trajectories)\n",
" traj.loc[:,'x'] = traj['startX'] + (traj['endX'] - traj['startX']) * (t - traj['startTime']) / (traj['endTime'] - traj['startTime'])\n",
" traj.loc[:,'y'] = traj['startY'] + (traj['endY'] - traj['startY']) * (t - traj['startTime']) / (traj['endTime'] - traj['startTime'])\n",
" traj = traj[traj.apply(lambda x: contains(x['x'], x['y'],rect), axis=1)]\n",
" return traj\n",
"\n",
"def density(t, rect, trajectories):\n",
" area = rect.get_width() * rect.get_height()\n",
" traj = filter_by_time_and_place(t, rect, trajectories)\n",
" number_of_peds = len(traj)\n",
" if number_of_peds == 0:\n",
" return 0\n",
" else:\n",
" return number_of_peds / area\n",
" \n",
"measurementArea = mpl.patches.Rectangle((16.3,6.0), 2.4, 2.0)\n",
"density1 = list(map(lambda t: density(t, measurementArea, c_real_trajectories), times))\n",
"density2 = list(map(lambda t: density(t, measurementArea, c_sim_trajecotories), times))\n",
"\n",
"fig_density = plt.figure(figsize=(10,5))\n",
"ax1_density = fig_density.add_subplot(121)\n",
"ax1_density.set_xlim(min(times),max(times))\n",
"ax1_density.set_ylim(0,6)\n",
"ax1_density.set_xlabel(\"time\")\n",
"ax1_density.set_ylabel(\"density\")\n",
"plt.plot(times, density1)\n",
"\n",
"ax2_density = fig_density.add_subplot(122)\n",
"ax2_density.set_xlim(min(times),max(times))\n",
"ax2_density.set_xlabel(\"time\")\n",
"ax2_density.set_ylabel(\"density\")\n",
"ax2_density.set_ylim(0,6)\n",
"plt.plot(times, density2)\n",
"plt.show()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Plot of fundamental diagrams using method c"
]
},
{
......@@ -609,7 +843,15 @@
"metadata": {},
"outputs": [],
"source": [
"to_postVis(data)"
"fig_fundamental = plt.figure(figsize=(10,5))\n",
"ax1_fundamental = fig_fundamental.add_subplot(111)\n",
"#ax1_fundamental.set_xlim(min(times),max(times))\n",
"ax1_fundamental.set_ylim(0,6)\n",
"ax1_fundamental.set_xlabel(\"velocity\")\n",
"ax1_fundamental.set_ylabel(\"density\")\n",
"plt.plot(velocity1, density1, '*')\n",
"plt.plot(velocity2, density2, '*')\n",
"plt.show()"
]
}
],
......@@ -629,7 +871,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.6.7"
"version": "3.6.8"
}
},
"nbformat": 4,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment