Commit 518edf128cc8c582389c1db882ceada9f40f5155

Authored by David Mayerich
2 parents 8705dfb0 58674036

Merge branch 'JACK' into 'master'

fix minor error for loading and rendering networks from swc files

See merge request !24
stim/biomodels/network.h
@@ -390,6 +390,7 @@ public: @@ -390,6 +390,7 @@ public:
390 } 390 }
391 } 391 }
392 392
  393 + //load a network from an SWC file
393 void load_swc(std::string filename) { 394 void load_swc(std::string filename) {
394 stim::swc<T> S; // create swc variable 395 stim::swc<T> S; // create swc variable
395 S.load(filename); // load the node information 396 S.load(filename); // load the node information
@@ -401,16 +402,23 @@ public: @@ -401,16 +402,23 @@ public:
401 for (unsigned int l = 1; l < S.numL(); l++) { // for every node starts from second one 402 for (unsigned int l = 1; l < S.numL(); l++) { // for every node starts from second one
402 NT.push_back(S.node[l].type); 403 NT.push_back(S.node[l].type);
403 stim::centerline<T> c3(2); // every fiber contains two vertices 404 stim::centerline<T> c3(2); // every fiber contains two vertices
404 - int id = S.node[l].parent_idx - 1;  
405 - c3[0] = S.node[id].point; // store the begin vertex 405 + int p_idx = S.node[l].parent_idx - 1; // get the parent node loop idx of current node
  406 + c3[0] = S.node[p_idx].point; // store the begin vertex
406 c3[1] = S.node[l].point; // store the end vertex 407 c3[1] = S.node[l].point; // store the end vertex
407 408
408 c3.update(); // update the L information 409 c3.update(); // update the L information
409 -  
410 - edge new_edge(c3); // contains two points  
411 - 410 +
  411 + stim::cylinder<T> C3(c3); // create a new cylinder in order to copy the origin radius information
  412 + // upadate the R information
  413 + std::vector<T> radius;
  414 + radius.push_back(S.node[p_idx].radius);
  415 + radius.push_back(S.node[l].radius);
  416 + C3.copy_r(radius);
  417 +
  418 + edge new_edge(C3); // contains two points
  419 +
412 //get the first and last vertex IDs for the line 420 //get the first and last vertex IDs for the line
413 - i[0] = S.node[id].idx; //get the SWC ID for the start point 421 + i[0] = S.node[p_idx].idx; //get the SWC ID for the start point
414 i[1] = S.node[l].idx; //get the SWC ID for the end point 422 i[1] = S.node[l].idx; //get the SWC ID for the end point
415 423
416 std::vector<unsigned>::iterator it; //create an iterator for searching the id2vert array 424 std::vector<unsigned>::iterator it; //create an iterator for searching the id2vert array
@@ -471,7 +479,7 @@ public: @@ -471,7 +479,7 @@ public:
471 stim::network<T> resample(T spacing){ 479 stim::network<T> resample(T spacing){
472 stim::network<T> n; //create a new network that will be an exact copy, with resampled fibers 480 stim::network<T> n; //create a new network that will be an exact copy, with resampled fibers
473 n.V = V; //copy all vertices 481 n.V = V; //copy all vertices
474 - 482 + n.NT = NT; //copy all the neuronal type information
475 n.E.resize(edges()); //allocate space for the edge list 483 n.E.resize(edges()); //allocate space for the edge list
476 484
477 //copy all fibers, resampling them in the process 485 //copy all fibers, resampling them in the process
stim/visualization/cylinder.h
@@ -54,6 +54,12 @@ public: @@ -54,6 +54,12 @@ public:
54 //add_mag(r); 54 //add_mag(r);
55 } 55 }
56 56
  57 + //copy the original radius
  58 + void copy_r(std::vector<T> radius) {
  59 + for (unsigned i = 0; i < radius.size(); i++)
  60 + R[i] = radius[i];
  61 + }
  62 +
57 ///Returns magnitude i at the given length into the fiber (based on the pvalue). 63 ///Returns magnitude i at the given length into the fiber (based on the pvalue).
58 ///Interpolates the position along the line. 64 ///Interpolates the position along the line.
59 ///@param l: the location of the in the cylinder. 65 ///@param l: the location of the in the cylinder.
@@ -175,20 +181,23 @@ public: @@ -175,20 +181,23 @@ public:
175 /// Integrates a magnitude value along the cylinder. 181 /// Integrates a magnitude value along the cylinder.
176 /// @param m is the magnitude value to be integrated (this is usually the radius) 182 /// @param m is the magnitude value to be integrated (this is usually the radius)
177 T integrate() { 183 T integrate() {
  184 + T sum = 0; //initialize the integral to zero
  185 + if (L.size() == 1)
  186 + return sum;
  187 + else {
  188 + T m0, m1; //allocate space for both magnitudes in a single segment
  189 + m0 = R[0]; //initialize the first point and magnitude to the first point in the cylinder
  190 + T len = L[1]; //allocate space for the segment length
178 191
179 - T sum = 0; //initialize the integral to zero  
180 - T m0, m1; //allocate space for both magnitudes in a single segment  
181 - m0 = R[0]; //initialize the first point and magnitude to the first point in the cylinder  
182 - T len = L[1]; //allocate space for the segment length  
183 -  
184 -  
185 - for (unsigned p = 1; p < size(); p++) { //for every consecutive point in the cylinder  
186 - m1 = R[p];  
187 - if (p > 1) len = (L[p] - L[p - 1]); //calculate the segment length using the L array  
188 - sum += (m0 + m1) / (T)2.0 * len; //add the average magnitude, weighted by the segment length  
189 - m0 = m1; //move to the next segment by shifting points 192 +
  193 + for (unsigned p = 1; p < size(); p++) { //for every consecutive point in the cylinder
  194 + m1 = R[p];
  195 + if (p > 1) len = (L[p] - L[p - 1]); //calculate the segment length using the L array
  196 + sum += (m0 + m1) / (T)2.0 * len; //add the average magnitude, weighted by the segment length
  197 + m0 = m1; //move to the next segment by shifting points
  198 + }
  199 + return sum; //return the integral
190 } 200 }
191 - return sum; //return the integral  
192 } 201 }
193 202
194 /// Resamples the cylinder to provide a maximum distance of "spacing" between centerline points. All current 203 /// Resamples the cylinder to provide a maximum distance of "spacing" between centerline points. All current
stim/visualization/gl_network.h
@@ -43,28 +43,6 @@ public: @@ -43,28 +43,6 @@ public:
43 return bb; //return the bounding box 43 return bb; //return the bounding box
44 } 44 }
45 45
46 - //void renderCylinder(T x1, T y1, T z1, T x2, T y2, T z2, T radius, int subdivisions) {  
47 - // T dx = x2 - x1;  
48 - // T dy = y2 - y1;  
49 - // T dz = z2 - z1;  
50 - // /// handle the degenerate case with an approximation  
51 - // if (dz == 0)  
52 - // dz = .00000001;  
53 - // T d = sqrt(dx*dx + dy*dy + dz*dz);  
54 - // T ax = 57.2957795*acos(dz / d); // 180°/pi  
55 - // if (dz < 0.0)  
56 - // ax = -ax;  
57 - // T rx = -dy*dz;  
58 - // T ry = dx*dz;  
59 -  
60 - // glPushMatrix();  
61 - // glTranslatef(x1, y1, z1);  
62 - // glRotatef(ax, rx, ry, 0.0);  
63 -  
64 - // glutSolidCylinder(radius, d, subdivisions, 1);  
65 - // glPopMatrix();  
66 - //}  
67 -  
68 ///render cylinder based on points from the top/bottom hat 46 ///render cylinder based on points from the top/bottom hat
69 ///@param C1 set of points from one of the hat 47 ///@param C1 set of points from one of the hat
70 void renderCylinder(std::vector< stim::vec3<T> > C1, std::vector< stim::vec3<T> > C2) { 48 void renderCylinder(std::vector< stim::vec3<T> > C1, std::vector< stim::vec3<T> > C2) {
@@ -114,7 +92,7 @@ public: @@ -114,7 +92,7 @@ public:
114 dlist = glGenLists(1); // generate a display list 92 dlist = glGenLists(1); // generate a display list
115 glNewList(dlist, GL_COMPILE); // start a new display list 93 glNewList(dlist, GL_COMPILE); // start a new display list
116 for (unsigned e = 0; e < E.size(); e++) { 94 for (unsigned e = 0; e < E.size(); e++) {
117 - int type = NT[e]; 95 + int type = NT[e]; // get the neuronal type
118 switch (type) { 96 switch (type) {
119 case 0: 97 case 0:
120 glColor3f(1.0f, 1.0f, 1.0f); // white for undefined 98 glColor3f(1.0f, 1.0f, 1.0f); // white for undefined
@@ -187,8 +165,8 @@ public: @@ -187,8 +165,8 @@ public:
187 glCallList(dlist); // render the display list 165 glCallList(dlist); // render the display list
188 } 166 }
189 167
190 - ///render the network centerline as a series of line strips  
191 - ///colors is based on metric values 168 + ///render the network centerline as a series of line strips(when loading at least two networks, otherwise using glCenterline0())
  169 + ///colors are based on metric values
192 void glCenterline(){ 170 void glCenterline(){
193 171
194 if(!glIsList(dlist)){ //if dlist isn't a display list, create it 172 if(!glIsList(dlist)){ //if dlist isn't a display list, create it
@@ -208,11 +186,39 @@ public: @@ -208,11 +186,39 @@ public:
208 glCallList(dlist); //render the display list 186 glCallList(dlist); //render the display list
209 } 187 }
210 188
  189 + ///render the network cylinder as a series of tubes
  190 + ///colors are based on metric values
  191 + void glCylinder() {
  192 + if (!glIsList(dlist)) { //if dlist isn't a display list, create it
  193 + dlist = glGenLists(1); //generate a display list
  194 + glNewList(dlist, GL_COMPILE); //start a new display list
  195 + for (unsigned e = 0; e < E.size(); e++) { //for each edge in the network
  196 + for (unsigned p = 1; p < E[e].size(); p++) { // for each point on that edge
  197 + stim::circle<T> C1 = E[e].circ(p - 1);
  198 + stim::circle<T> C2 = E[e].circ(p);
  199 + C1.set_R(10); // scale the circle to the same
  200 + C2.set_R(10);
  201 + std::vector< stim::vec3<T> >Cp1 = C1.points(20);
  202 + std::vector< stim::vec3<T> >Cp2 = C2.points(20);
  203 + glBegin(GL_QUAD_STRIP);
  204 + for (unsigned i = 0; i < Cp1.size(); i++) { // for every point on the circle
  205 + glVertex3f(Cp1[i][0], Cp1[i][1], Cp1[i][2]);
  206 + glVertex3f(Cp2[i][0], Cp2[i][1], Cp2[i][2]);
  207 + glTexCoord1f(E[e].r(p));
  208 + }
  209 + glEnd();
  210 + } //set the texture coordinate based on the specified magnitude index
  211 + }
  212 + glEndList(); //end the display list
  213 + }
  214 + glCallList(dlist); //render the display list
  215 + }
  216 +
211 ///render the GT network cylinder as series of tubes 217 ///render the GT network cylinder as series of tubes
212 ///@param dlist1 is the display list 218 ///@param dlist1 is the display list
213 ///@param map is the mapping relationship between two networks 219 ///@param map is the mapping relationship between two networks
214 ///@param colormap is the random generated color set for render 220 ///@param colormap is the random generated color set for render
215 - void glCylinderGT(GLuint &dlist1, std::vector<unsigned> map, std::vector<T> colormap) { 221 + void glRandColorCylinder1(GLuint &dlist1, std::vector<unsigned> map, std::vector<T> colormap) {
216 if (!glIsList(dlist1)) { // if dlist1 isn't a display list, create it 222 if (!glIsList(dlist1)) { // if dlist1 isn't a display list, create it
217 dlist1 = glGenLists(1); // generate a display list 223 dlist1 = glGenLists(1); // generate a display list
218 glNewList(dlist1, GL_COMPILE); // start a new display list 224 glNewList(dlist1, GL_COMPILE); // start a new display list
@@ -258,11 +264,57 @@ public: @@ -258,11 +264,57 @@ public:
258 glCallList(dlist1); 264 glCallList(dlist1);
259 } 265 }
260 266
  267 + void glRandColorCylinder1_swc(GLuint &dlist1, std::vector<unsigned> map, std::vector<T> colormap) {
  268 + if (!glIsList(dlist1)) { // if dlist1 isn't a display list, create it
  269 + dlist1 = glGenLists(1); // generate a display list
  270 + glNewList(dlist1, GL_COMPILE); // start a new display list
  271 + for (unsigned e = 0; e < E.size(); e++) { // for each edge in the network
  272 + if (map[e] != unsigned(-1)) {
  273 + glColor3f(colormap[e * 3 + 0], colormap[e * 3 + 1], colormap[e * 3 + 2]);
  274 + for (unsigned p = 1; p < E[e].size(); p++) {// for each point on that edge
  275 + stim::circle<T> C1 = E[e].circ(p - 1);
  276 + stim::circle<T> C2 = E[e].circ(p);
  277 + C1.set_R(0.5); // scale the circle to the same
  278 + C2.set_R(0.5);
  279 + std::vector< stim::vec3<T> >Cp1 = C1.points(20);
  280 + std::vector< stim::vec3<T> >Cp2 = C2.points(20);
  281 + renderCylinder(Cp1, Cp2);
  282 + }
  283 + }
  284 + else {
  285 + glColor3f(1.0f, 1.0f, 1.0f); // white color for the un-mapping edges
  286 + for (unsigned p = 1; p < E[e].size(); p++) { // for each point on that edge
  287 + stim::circle<T> C1 = E[e].circ(p - 1);
  288 + stim::circle<T> C2 = E[e].circ(p);
  289 + C1.set_R(0.5); // scale the circle to the same
  290 + C2.set_R(0.5);
  291 + std::vector< stim::vec3<T> >Cp1 = C1.points(20);
  292 + std::vector< stim::vec3<T> >Cp2 = C2.points(20);
  293 + renderCylinder(Cp1, Cp2);
  294 + }
  295 + }
  296 + }
  297 + for (unsigned v = 0; v < V.size(); v++) {
  298 + size_t num_edge = V[v].e[0].size() + V[v].e[1].size();
  299 + if (num_edge > 1) { // if it is the joint vertex
  300 + glColor3f(0.3, 0.3, 0.3); // gray color
  301 + renderBall(V[v][0], V[v][1], V[v][2], 1, 20);
  302 + }
  303 + else { // if it is the terminal vertex
  304 + glColor3f(0.6, 0.6, 0.6); // more white gray
  305 + renderBall(V[v][0], V[v][1], V[v][2], 1, 20);
  306 + }
  307 + }
  308 + glEndList();
  309 + }
  310 + glCallList(dlist1);
  311 + }
  312 +
261 ///render the T network cylinder as series of tubes 313 ///render the T network cylinder as series of tubes
262 ///@param dlist2 is the display list 314 ///@param dlist2 is the display list
263 ///@param map is the mapping relationship between two networks 315 ///@param map is the mapping relationship between two networks
264 ///@param colormap is the random generated color set for render 316 ///@param colormap is the random generated color set for render
265 - void glCylinderT(GLuint &dlist2, std::vector<unsigned> map, std::vector<T> colormap) { 317 + void glRandColorCylinder2(GLuint &dlist2, std::vector<unsigned> map, std::vector<T> colormap) {
266 if (!glIsList(dlist2)) { 318 if (!glIsList(dlist2)) {
267 dlist2 = glGenLists(1); 319 dlist2 = glGenLists(1);
268 glNewList(dlist2, GL_COMPILE); 320 glNewList(dlist2, GL_COMPILE);
@@ -308,11 +360,57 @@ public: @@ -308,11 +360,57 @@ public:
308 glCallList(dlist2); 360 glCallList(dlist2);
309 } 361 }
310 362
  363 + void glRandColorCylinder2_swc(GLuint &dlist2, std::vector<unsigned> map, std::vector<T> colormap) {
  364 + if (!glIsList(dlist2)) {
  365 + dlist2 = glGenLists(1);
  366 + glNewList(dlist2, GL_COMPILE);
  367 + for (unsigned e = 0; e < E.size(); e++) { // for each edge in the network
  368 + if (map[e] != unsigned(-1)) {
  369 + glColor3f(colormap[map[e] * 3 + 0], colormap[map[e] * 3 + 1], colormap[map[e] * 3 + 2]);
  370 + for (unsigned p = 0; p < E[e].size() - 1; p++) {// for each point on that edge
  371 + stim::circle<T> C1 = E[e].circ(p);
  372 + stim::circle<T> C2 = E[e].circ(p + 1);
  373 + C1.set_R(0.5); // scale the circle to the same
  374 + C2.set_R(0.5);
  375 + std::vector< stim::vec3<T> >Cp1 = C1.points(20);
  376 + std::vector< stim::vec3<T> >Cp2 = C2.points(20);
  377 + renderCylinder(Cp1, Cp2);
  378 + }
  379 + }
  380 + else {
  381 + glColor3f(1.0f, 1.0f, 1.0f); // white color for the un-mapping edges
  382 + for (unsigned p = 0; p < E[e].size() - 1; p++) {// for each point on that edge
  383 + stim::circle<T> C1 = E[e].circ(p);
  384 + stim::circle<T> C2 = E[e].circ(p + 1);
  385 + C1.set_R(0.5); // scale the circle to the same
  386 + C2.set_R(0.5);
  387 + std::vector< stim::vec3<T> >Cp1 = C1.points(20);
  388 + std::vector< stim::vec3<T> >Cp2 = C2.points(20);
  389 + renderCylinder(Cp1, Cp2);
  390 + }
  391 + }
  392 + }
  393 + for (unsigned v = 0; v < V.size(); v++) {
  394 + size_t num_edge = V[v].e[0].size() + V[v].e[1].size();
  395 + if (num_edge > 1) { // if it is the joint vertex
  396 + glColor3f(0.3, 0.3, 0.3); // gray color
  397 + renderBall(V[v][0], V[v][1], V[v][2], 1, 20);
  398 + }
  399 + else { // if it is the terminal vertex
  400 + glColor3f(0.6, 0.6, 0.6); // more white gray
  401 + renderBall(V[v][0], V[v][1], V[v][2], 1, 20);
  402 + }
  403 + }
  404 + glEndList();
  405 + }
  406 + glCallList(dlist2);
  407 + }
  408 +
311 /// Render the GT network centerline as a series of line strips in random different color 409 /// Render the GT network centerline as a series of line strips in random different color
312 ///@param dlist1 is the display list 410 ///@param dlist1 is the display list
313 ///@param map is the mapping relationship between two networks 411 ///@param map is the mapping relationship between two networks
314 ///@param colormap is the random generated color set for render 412 ///@param colormap is the random generated color set for render
315 - void glRandColorCenterlineGT(GLuint &dlist1, std::vector<unsigned> map, std::vector<T> colormap) { 413 + void glRandColorCenterline1(GLuint &dlist1, std::vector<unsigned> map, std::vector<T> colormap) {
316 if (!glIsList(dlist1)) { 414 if (!glIsList(dlist1)) {
317 dlist1 = glGenLists(1); 415 dlist1 = glGenLists(1);
318 glNewList(dlist1, GL_COMPILE); 416 glNewList(dlist1, GL_COMPILE);
@@ -343,7 +441,7 @@ public: @@ -343,7 +441,7 @@ public:
343 ///@param dlist2 is the display list 441 ///@param dlist2 is the display list
344 ///@param map is the mapping relationship between two networks 442 ///@param map is the mapping relationship between two networks
345 ///@param colormap is the random generated color set for render 443 ///@param colormap is the random generated color set for render
346 - void glRandColorCenterlineT(GLuint &dlist2, std::vector<unsigned> map, std::vector<T> colormap) { 444 + void glRandColorCenterline2(GLuint &dlist2, std::vector<unsigned> map, std::vector<T> colormap) {
347 if (!glIsList(dlist2)) { 445 if (!glIsList(dlist2)) {
348 dlist2 = glGenLists(1); 446 dlist2 = glGenLists(1);
349 glNewList(dlist2, GL_COMPILE); 447 glNewList(dlist2, GL_COMPILE);
@@ -444,6 +542,29 @@ public: @@ -444,6 +542,29 @@ public:
444 // glCallList(dlist2); 542 // glCallList(dlist2);
445 //} 543 //}
446 544
  545 +
  546 + //void renderCylinder(T x1, T y1, T z1, T x2, T y2, T z2, T radius, int subdivisions) {
  547 + // T dx = x2 - x1;
  548 + // T dy = y2 - y1;
  549 + // T dz = z2 - z1;
  550 + // /// handle the degenerate case with an approximation
  551 + // if (dz == 0)
  552 + // dz = .00000001;
  553 + // T d = sqrt(dx*dx + dy*dy + dz*dz);
  554 + // T ax = 57.2957795*acos(dz / d); // 180°/pi
  555 + // if (dz < 0.0)
  556 + // ax = -ax;
  557 + // T rx = -dy*dz;
  558 + // T ry = dx*dz;
  559 +
  560 + // glPushMatrix();
  561 + // glTranslatef(x1, y1, z1);
  562 + // glRotatef(ax, rx, ry, 0.0);
  563 +
  564 + // glutSolidCylinder(radius, d, subdivisions, 1);
  565 + // glPopMatrix();
  566 + //}
  567 +
447 }; //end stim::gl_network class 568 }; //end stim::gl_network class
448 }; //end stim namespace 569 }; //end stim namespace
449 570
stim/visualization/swc.h
@@ -20,7 +20,7 @@ namespace stim { @@ -20,7 +20,7 @@ namespace stim {
20 enum node_information { INTEGER_LABEL, NEURONAL_TYPE, X_COORDINATE, Y_COORDINATE, Z_COORDINATE, RADIUS, PARENT_LABEL }; 20 enum node_information { INTEGER_LABEL, NEURONAL_TYPE, X_COORDINATE, Y_COORDINATE, Z_COORDINATE, RADIUS, PARENT_LABEL };
21 21
22 public: 22 public:
23 - int idx; // the index of current node start from 1(ambiguity) 23 + int idx; // the index of current node start from 1(original index from the file)
24 neuronal_type type; // the type of neuronal segmemt 24 neuronal_type type; // the type of neuronal segmemt
25 stim::vec3<T> point; // the point coordinates 25 stim::vec3<T> point; // the point coordinates
26 T radius; // the radius at current node 26 T radius; // the radius at current node
@@ -44,7 +44,7 @@ namespace stim { @@ -44,7 +44,7 @@ namespace stim {
44 // for each information contained in the node point we got 44 // for each information contained in the node point we got
45 for (unsigned int i = 0; i < p.size(); i++) { 45 for (unsigned int i = 0; i < p.size(); i++) {
46 std::stringstream ss(p[i]); // create a stringstream object for casting 46 std::stringstream ss(p[i]); // create a stringstream object for casting
47 - 47 +
48 // store different information 48 // store different information
49 switch (i) { 49 switch (i) {
50 case INTEGER_LABEL: 50 case INTEGER_LABEL:
@@ -103,8 +103,8 @@ namespace stim { @@ -103,8 +103,8 @@ namespace stim {
103 std::string line; 103 std::string line;
104 // skip comment 104 // skip comment
105 while (getline(infile, line)) { 105 while (getline(infile, line)) {
106 - if ('#' == line[0]) // if it is comment line  
107 - continue; 106 + if ('#' == line[0] || line.empty()) // if it is comment line or empty line
  107 + continue; // keep read
108 else 108 else
109 break; 109 break;
110 } 110 }
@@ -144,7 +144,7 @@ namespace stim { @@ -144,7 +144,7 @@ namespace stim {
144 144
145 std::string line; 145 std::string line;
146 while (getline(infile, line)) { 146 while (getline(infile, line)) {
147 - if ('#' == line[0]) { 147 + if ('#' == line[0] || line.empty()) {
148 std::cout << line << std::endl; // print the comment line by line 148 std::cout << line << std::endl; // print the comment line by line
149 } 149 }
150 else 150 else
@@ -165,7 +165,7 @@ namespace stim { @@ -165,7 +165,7 @@ namespace stim {
165 if (node[i].parent_idx != node[i - 1].parent_idx) 165 if (node[i].parent_idx != node[i - 1].parent_idx)
166 cur_level = node[node[i].parent_idx - 1].level + 1; 166 cur_level = node[node[i].parent_idx - 1].level + 1;
167 node[i].level = cur_level; 167 node[i].level = cur_level;
168 - int tmp_parent_idx = node[i].parent_idx - 1; // get the parent node loop idx of current node 168 + int tmp_parent_idx = node[i].parent_idx - 1; // get the parent node loop idx of current node
169 node[tmp_parent_idx].son_idx.push_back(i + 1); // son_idx stores the real idx = loop idx + 1 169 node[tmp_parent_idx].son_idx.push_back(i + 1); // son_idx stores the real idx = loop idx + 1
170 } 170 }
171 } 171 }