Isis 3 Programmer Reference
ProcessRubberSheet.cpp
Go to the documentation of this file.
1 
23 #include <iostream>
24 #include <iomanip>
25 
26 #include <QVector>
27 
28 #include "Affine.h"
29 #include "BasisFunction.h"
30 #include "BoxcarCachingAlgorithm.h"
31 #include "Brick.h"
32 #include "Interpolator.h"
33 #include "LeastSquares.h"
34 #include "Portal.h"
35 #include "ProcessRubberSheet.h"
36 #include "TileManager.h"
37 #include "Transform.h"
39 
40 using namespace std;
41 
42 
43 namespace Isis {
50  ProcessRubberSheet::ProcessRubberSheet(int startSize, int endSize) {
51 
52  p_bandChangeFunct = NULL;
53 
54  // Information used only in the tile transform method (StartProcess)
55  p_forceSamp = Null;
56  p_forceLine = Null;
57  p_startQuadSize = startSize;
58  p_endQuadSize = endSize;
59 
60  // Patch parameters used only in the patch transform (processPatchTransform)
61  m_patchStartSample = 1;
62  m_patchStartLine = 1;
63  m_patchSamples = 5;
64  m_patchLines = 5;
65  m_patchSampleIncrement = 4;
66  m_patchLineIncrement = 4;
67  };
68 
69 
117  void ProcessRubberSheet::setPatchParameters(int startSample, int startLine,
118  int samples, int lines,
119  int sampleIncrement, int lineIncrement) {
120 
121  m_patchStartSample = startSample;
122  m_patchStartLine = startLine;
123  m_patchSamples = samples;
124  m_patchLines = lines;
125  m_patchSampleIncrement = sampleIncrement;
126  m_patchLineIncrement = lineIncrement;
127  }
128 
129 
147  void ProcessRubberSheet::StartProcess(Transform &trans,
148  Interpolator &interp) {
149 
150  // Error checks ... there must be one input and one output
151  if (InputCubes.size() != 1) {
152  string m = "You must specify exactly one input cube";
153  throw IException(IException::Programmer, m, _FILEINFO_);
154  }
155  else if (OutputCubes.size() != 1) {
156  string m = "You must specify exactly one output cube";
157  throw IException(IException::Programmer, m, _FILEINFO_);
158  }
159 
160  // allocate the sampMap/lineMap vectors
161  p_lineMap.resize(p_startQuadSize);
162  p_sampMap.resize(p_startQuadSize);
163 
164  for (unsigned int pos = 0; pos < p_lineMap.size(); pos++) {
165  p_lineMap[pos].resize(p_startQuadSize);
166  p_sampMap[pos].resize(p_startQuadSize);
167  }
168 
169  // Create a tile manager for the output file
170  TileManager otile(*OutputCubes[0], p_startQuadSize, p_startQuadSize);
171 
172  // Create a portal buffer for the input file
173  Portal iportal(interp.Samples(), interp.Lines(),
174  InputCubes[0]->pixelType() ,
175  interp.HotSample(), interp.HotLine());
176 
177  // Start the progress meter
178  p_progress->SetMaximumSteps(otile.Tiles());
179  p_progress->CheckStatus();
180 
181  if (p_bandChangeFunct == NULL) {
182  // A portal could read up to four chunks so we need to cache four times the number of bands to
183  // minimize I/O thrashing
184  InputCubes[0]->addCachingAlgorithm(new UniqueIOCachingAlgorithm(2 * InputCubes[0]->bandCount()));
185  OutputCubes[0]->addCachingAlgorithm(new BoxcarCachingAlgorithm());
186 
187  long long int tilesPerBand = otile.Tiles() / OutputCubes[0]->bandCount();
188 
189  for (long long int tile = 1; tile <= tilesPerBand; tile++) {
190  bool useLastTileMap = false;
191  for (int band = 1; band <= OutputCubes[0]->bandCount(); band++) {
192  otile.SetTile(tile, band);
193 
194  if (p_startQuadSize <= 2) {
195  SlowGeom(otile, iportal, trans, interp);
196  }
197  else {
198  QuadTree(otile, iportal, trans, interp, useLastTileMap);
199  }
200 
201  useLastTileMap = true;
202 
203  OutputCubes[0]->write(otile);
204  p_progress->CheckStatus();
205  }
206  }
207  }
208  else {
209  int lastOutputBand = -1;
210 
211  for (otile.begin(); !otile.end(); otile++) {
212  // Keep track of the current band
213  if (lastOutputBand != otile.Band()) {
214  lastOutputBand = otile.Band();
215  // Call an application function if the band number changes
216  p_bandChangeFunct(lastOutputBand);
217  }
218 
219  if (p_startQuadSize <= 2) {
220  SlowGeom(otile, iportal, trans, interp);
221  }
222  else {
223  QuadTree(otile, iportal, trans, interp, false);
224  }
225 
226  OutputCubes[0]->write(otile);
227  p_progress->CheckStatus();
228  }
229  }
230 
231  p_sampMap.clear();
232  p_lineMap.clear();
233  }
234 
235 
246  void ProcessRubberSheet::BandChange(void (*funct)(const int band)) {
247 
248  p_bandChangeFunct = funct;
249  }
250 
251 
252  void ProcessRubberSheet::SlowGeom(TileManager &otile, Portal &iportal,
253  Transform &trans, Interpolator &interp) {
254 
255  double outputSamp, outputLine;
256  double inputSamp, inputLine;
257  int outputBand = otile.Band();
258 
259  for (int i = 0; i < otile.size(); i++) {
260  outputSamp = otile.Sample(i);
261  outputLine = otile.Line(i);
262  // Use the defined transform to find out what input pixel the output
263  // pixel came from
264  if (trans.Xform(inputSamp, inputLine, outputSamp, outputLine)) {
265  if ((inputSamp < 0.5) || (inputLine < 0.5) ||
266  (inputLine > InputCubes[0]->lineCount() + 0.5) ||
267  (inputSamp > InputCubes[0]->sampleCount() + 0.5)) {
268  otile[i] = NULL8;
269  }
270  else {
271  // Set the position of the portal in the input cube
272  iportal.SetPosition(inputSamp, inputLine, outputBand);
273  InputCubes[0]->read(iportal);
274  otile[i] = interp.Interpolate(inputSamp, inputLine,
275  iportal.DoubleBuffer());
276  }
277  }
278  else {
279  otile[i] = NULL8;
280  }
281  }
282  }
283 
284 
285  void ProcessRubberSheet::QuadTree(TileManager &otile, Portal &iportal,
286  Transform &trans, Interpolator &interp,
287  bool useLastTileMap) {
288 
289  // Initializations
290  vector<Quad *> quadTree;
291 
292  if (!useLastTileMap) {
293  // Set up the boundaries of the full tile
294  Quad *quad = new Quad;
295  quad->sline = otile.Line();
296  quad->ssamp = otile.Sample();
297 
298  quad->eline = otile.Line(otile.size() - 1);
299  quad->esamp = otile.Sample(otile.size() - 1);
300  quad->slineTile = otile.Line();
301  quad->ssampTile = otile.Sample();
302 
303  quadTree.push_back(quad);
304 
305  // Loop and compute the input coordinates filling the maps
306  // until the quad tree is empty
307  while (quadTree.size() > 0) {
308  ProcessQuad(quadTree, trans, p_lineMap, p_sampMap);
309  }
310  }
311 
312  // Apply the map to the output tile
313  int outputBand = otile.Band();
314  for (int i = 0, line = 0; line < p_startQuadSize; line++) {
315  for (int samp = 0; samp < p_startQuadSize; samp++, i++) {
316  double inputLine = p_lineMap[line][samp];
317  double inputSamp = p_sampMap[line][samp];
318  if (inputLine != NULL8) {
319  iportal.SetPosition(inputSamp, inputLine, outputBand);
320  InputCubes[0]->read(iportal);
321  otile[i] = interp.Interpolate(inputSamp, inputLine,
322  iportal.DoubleBuffer());
323  }
324  else {
325  otile[i] = NULL8;
326  }
327  }
328  }
329  }
330 
331 
344  bool ProcessRubberSheet::TestLine(Transform &trans, int ssamp, int esamp,
345  int sline, int eline, int increment) {
346 
347  for (int line = sline; line <= eline; line += increment) {
348  for (int sample = ssamp; sample <= esamp; sample += increment) {
349  double sjunk = 0.0;
350  double ljunk = 0.0;
351 
352  if (trans.Xform(sjunk, ljunk, sample, line)) {
353  return true;
354  }
355  }
356  }
357 
358  return false;
359  }
360 
361 
362  // Process a quad trying to find input positions for output positions
363  void ProcessRubberSheet::ProcessQuad(std::vector<Quad *> &quadTree,
364  Transform &trans,
365  std::vector< std::vector<double> > &lineMap,
366  std::vector< std::vector<double> > &sampMap) {
367 
368  Quad *quad = quadTree[0];
369  double oline[4], osamp[4];
370  double iline[4], isamp[4];
371 
372  // Try to convert the upper left corner to input coordinates
373  int badCorner = 0;
374  oline[0] = quad->sline;
375  osamp[0] = quad->ssamp;
376  if (!trans.Xform(isamp[0], iline[0], osamp[0], oline[0])) {
377  badCorner++;
378  }
379 
380  // Now try the upper right corner
381  oline[1] = quad->sline;
382  osamp[1] = quad->esamp;
383  if (!trans.Xform(isamp[1], iline[1], osamp[1], oline[1])) {
384  badCorner++;
385  }
386 
387  // Now try the lower left corner
388  oline[2] = quad->eline;
389  osamp[2] = quad->ssamp;
390  if (!trans.Xform(isamp[2], iline[2], osamp[2], oline[2])) {
391  badCorner++;
392  }
393 
394  // Now try the lower right corner
395  oline[3] = quad->eline;
396  osamp[3] = quad->esamp;
397  if (!trans.Xform(isamp[3], iline[3], osamp[3], oline[3])) {
398  badCorner++;
399  }
400 
401  // If all four corners are bad then walk the edges. If any points
402  // on the edges transform we will split the quad or
403  // if the quad is already small just transform everything
404  if (badCorner == 4) {
405  if ((quad->eline - quad->sline) < p_endQuadSize) {
406  SlowQuad(quadTree, trans, lineMap, sampMap);
407  }
408  else {
409  if (p_forceSamp != Null && p_forceLine != Null) {
410  if (p_forceSamp >= quad->ssamp && p_forceSamp <= quad->esamp &&
411  p_forceLine >= quad->sline && p_forceLine <= quad->eline) {
412  SplitQuad(quadTree);
413  return;
414  }
415  }
416 
417  int centerSample = (quad->ssamp + quad->esamp) / 2;
418  int centerLine = (quad->sline + quad->eline) / 2;
419 
420  // All 4 corner points have failed tests.
421  //
422  // If we find data around the quad by walking around a 2x2 grid in the
423  // box, then we need to split the quad. Check outside the box and
424  // interior crosshair.
425  //
426  // This is what we're walking:
427  // -----------
428  // | | |
429  // | | |
430  // |----|----|
431  // | | |
432  // | | |
433  // -----------
434  // Top Edge
435  if (TestLine(trans, quad->ssamp + 1, quad->esamp - 1,
436  quad->sline, quad->sline, 4) ||
437  // Bottom Edge
438  TestLine(trans, quad->ssamp + 1, quad->esamp - 1,
439  quad->eline, quad->eline, 4) ||
440  // Left Edge
441  TestLine(trans, quad->ssamp, quad->ssamp,
442  quad->sline + 1, quad->eline - 1, 4) ||
443  // Right Edge
444  TestLine(trans, quad->esamp, quad->esamp,
445  quad->sline + 1, quad->eline - 1, 4) ||
446  // Center Column
447  TestLine(trans, centerSample, centerSample,
448  quad->sline + 1, quad->eline - 1, 4) ||
449  // Center Row
450  TestLine(trans, quad->ssamp + 1, quad->esamp - 1,
451  centerLine, centerLine, 4)) {
452 
453 
454  SplitQuad(quadTree);
455  return;
456  }
457 
458  // Nothing in quad, fill with nulls
459  for (int i = quad->sline; i <= quad->eline; i++) {
460  for (int j = quad->ssamp; j <= quad->esamp; j++) {
461  lineMap[i-quad->slineTile][j-quad->ssampTile] = NULL8;
462  }
463  }
464  delete quad;
465  quadTree.erase(quadTree.begin());
466  }
467  return;
468  }
469 
470  // If all four corners are bad then assume the whole tile is bad
471  // Load the maps with nulls and delete the quad from the list
472  // Free memory too
473  // if (badCorner == 4) {
474  // for (int i=quad->sline; i<=quad->eline; i++) {
475  // for (int j=quad->ssamp; j<=quad->esamp; j++) {
476  // lineMap[i-quad->slineTile][j-quad->ssampTile] = NULL8;
477  // }
478  // }
479  // delete quad;
480  // quadTree.erase(quadTree.begin());
481  // return;
482  // }
483 
484  // See if any other corners are bad in which case we will need to
485  // split the quad into finer pieces. But lets not get ridiculous.
486  // If the split distance is small we might as well compute at every
487  // point
488  if (badCorner > 0) {
489  if ((quad->eline - quad->sline) < p_endQuadSize) {
490  SlowQuad(quadTree, trans, lineMap, sampMap);
491  }
492  else {
493  SplitQuad(quadTree);
494  }
495  return;
496  }
497 
498  // We have good corners ... create two equations using them
499  // iline = a*oline + b*osamp + c*oline*osamp + d
500  // isamp = e*oline + f*osamp + g*oline*osamp + h
501  // Start by setting up a 4x4 matrix
502  double A[4][4];
503  for (int i = 0; i < 4; i++) {
504  A[i][0] = oline[i];
505  A[i][1] = osamp[i];
506  A[i][2] = oline[i] * osamp[i];
507  A[i][3] = 1.0;
508  }
509 
510  // Make sure the determinate is non-zero, otherwise split it up again
511  // and hope for the best. If this happens it probably is because the
512  // transform is lame (bugged)
513  double detA;
514  if ((detA = Det4x4(A)) == 0.0) {
515  if ((quad->eline - quad->sline) < p_endQuadSize) {
516  SlowQuad(quadTree, trans, lineMap, sampMap);
517  }
518  else {
519  SplitQuad(quadTree);
520  }
521  return;
522  }
523 
524  // Substitute our desired answers into B to get the coefficients for the
525  // line dimension (Cramers Rule!!)
526  double B[4][4];
527  double lineCoef[4];
528  for (int j = 0; j < 4; j++) {
529  memmove(B, A, 16 * sizeof(double));
530 
531  for (int i = 0; i < 4; i++) {
532  B[i][j] = iline[i];
533  }
534 
535  lineCoef[j] = Det4x4(B) / detA;
536  }
537 
538  // Do it again to get the sample coefficients
539  double sampCoef[4];
540  for (int j = 0; j < 4; j++) {
541  memmove(B, A, 16 * sizeof(double));
542 
543  for (int i = 0; i < 4; i++) {
544  B[i][j] = isamp[i];
545  }
546 
547  sampCoef[j] = Det4x4(B) / detA;
548  }
549 
550  // Test the middle point to see if the equations are good
551  double quadMidLine = (quad->sline + quad->eline) / 2.0;
552  double quadMidSamp = (quad->ssamp + quad->esamp) / 2.0;
553  double midLine, midSamp;
554 
555  if (!trans.Xform(midSamp, midLine, quadMidSamp, quadMidLine)) {
556  if ((quad->eline - quad->sline) < p_endQuadSize) {
557  SlowQuad(quadTree, trans, lineMap, sampMap);
558  }
559  else {
560  SplitQuad(quadTree);
561  }
562  return;
563  }
564 
565  double cmidLine = lineCoef[0] * quadMidLine +
566  lineCoef[1] * quadMidSamp +
567  lineCoef[2] * quadMidLine * quadMidSamp +
568  lineCoef[3];
569 
570  double cmidSamp = sampCoef[0] * quadMidLine +
571  sampCoef[1] * quadMidSamp +
572  sampCoef[2] * quadMidLine * quadMidSamp +
573  sampCoef[3];
574 
575  if ((abs(cmidSamp - midSamp) > 0.5) || (abs(cmidLine - midLine) > 0.5)) {
576  if ((quad->eline - quad->sline) < p_endQuadSize) {
577  SlowQuad(quadTree, trans, lineMap, sampMap);
578  }
579  else {
580  SplitQuad(quadTree);
581  }
582  return;
583  }
584 
585  // Equations are suitably accurate.
586  // First compute input at the top corner of the output quad
587  double ulLine = lineCoef[0] * (double) quad->sline +
588  lineCoef[1] * (double) quad->ssamp +
589  lineCoef[2] * (double) quad->sline * (double) quad->ssamp +
590  lineCoef[3];
591 
592  double ulSamp = sampCoef[0] * (double) quad->sline +
593  sampCoef[1] * (double) quad->ssamp +
594  sampCoef[2] * (double) quad->sline * (double) quad->ssamp +
595  sampCoef[3];
596 
597  // Compute the derivate of the equations with respect to the
598  // output line as we will be changing the output line in a loop
599  double lineChangeWrLine = lineCoef[0] + lineCoef[2] * (double) quad->ssamp;
600  double sampChangeWrLine = sampCoef[0] + sampCoef[2] * (double) quad->ssamp;
601 
602  for (int ol = quad->sline; ol <= quad->eline; ol++) {
603  // Now Compute the derivates of the equations with respect to the
604  // output sample at the current line
605  double lineChangeWrSamp = lineCoef[1] + lineCoef[2] * (double) ol;
606  double sampChangeWrSamp = sampCoef[1] + sampCoef[2] * (double) ol;
607 
608  // Set first computed line to the left-edge position
609  double cline = ulLine;
610  double csamp = ulSamp;
611 
612  // Get pointers to speed processing
613  int startSamp = quad->ssamp - quad->ssampTile;
614  std::vector<double> &lineVect = lineMap[ol-quad->slineTile];
615  std::vector<double> &sampleVect = sampMap[ol-quad->slineTile];
616 
617  // Loop computing input positions for respective output positions
618  for (int os = quad->ssamp; os <= quad->esamp; os++) {
619  lineVect[startSamp] = cline;
620  sampleVect[startSamp] = csamp;
621 
622  startSamp ++;
623 
624  cline += lineChangeWrSamp;
625  csamp += sampChangeWrSamp;
626  }
627 
628  // Reposition at the left edge of the tile for the next line
629  ulLine += lineChangeWrLine;
630  ulSamp += sampChangeWrLine;
631  }
632 
633  // All done so remove the quad
634  delete quad;
635  quadTree.erase(quadTree.begin());
636  }
637 
638 
639  // Break input quad into four pieces
640  void ProcessRubberSheet::SplitQuad(std::vector<Quad *> &quadTree) {
641 
642  // Get the quad to split
643  Quad *quad = quadTree[0];
644  int n = (quad->eline - quad->sline + 1) / 2;
645 
646  // New upper left quad
647  Quad *q1 = new Quad;
648  *q1 = *quad;
649  q1->eline = quad->sline + n - 1;
650  q1->esamp = quad->ssamp + n - 1;
651  quadTree.push_back(q1);
652 
653  // New upper right quad
654  Quad *q2 = new Quad;
655  *q2 = *quad;
656  q2->eline = quad->sline + n - 1;
657  q2->ssamp = quad->ssamp + n;
658  quadTree.push_back(q2);
659 
660  // New lower left quad
661  Quad *q3 = new Quad;
662  *q3 = *quad;
663  q3->sline = quad->sline + n;
664  q3->esamp = quad->ssamp + n - 1;
665  quadTree.push_back(q3);
666 
667  // New lower right quad
668  Quad *q4 = new Quad;
669  *q4 = *quad;
670  q4->sline = quad->sline + n;
671  q4->ssamp = quad->ssamp + n;
672  quadTree.push_back(q4);
673 
674  // Remove the old quad since it has been split up
675  delete quad;
676  quadTree.erase(quadTree.begin());
677  }
678 
679 
680  // Slow quad computation for every output pixel
681  void ProcessRubberSheet::SlowQuad(std::vector<Quad *> &quadTree,
682  Transform &trans,
683  std::vector< std::vector<double> > &lineMap,
684  std::vector< std::vector<double> > &sampMap) {
685 
686  // Get the quad
687  Quad *quad = quadTree[0];
688  double iline, isamp;
689 
690  // Loop and do the slow computation of input position from output position
691  for (int oline = quad->sline; oline <= quad->eline; oline++) {
692  int lineIndex = oline - quad->slineTile;
693  for (int osamp = quad->ssamp; osamp <= quad->esamp; osamp++) {
694  int sampIndex = osamp - quad->ssampTile;
695  lineMap[lineIndex][sampIndex] = NULL8;
696  if (trans.Xform(isamp, iline, (double) osamp, (double) oline)) {
697  if ((isamp >= 0.5) ||
698  (iline >= 0.5) ||
699  (iline <= InputCubes[0]->lineCount() + 0.5) ||
700  (isamp <= InputCubes[0]->sampleCount() + 0.5)) {
701  lineMap[lineIndex][sampIndex] = iline;
702  sampMap[lineIndex][sampIndex] = isamp;
703  }
704  }
705  }
706  }
707 
708  // All done with the quad
709  delete quad;
710  quadTree.erase(quadTree.begin());
711  }
712 
713 
714  // Determinate method for 4x4 matrix using cofactor expansion
715  double ProcessRubberSheet::Det4x4(double m[4][4]) {
716 
717  double cofact[3][3];
718 
719  cofact [0][0] = m[1][1];
720  cofact [0][1] = m[1][2];
721  cofact [0][2] = m[1][3];
722  cofact [1][0] = m[2][1];
723  cofact [1][1] = m[2][2];
724  cofact [1][2] = m[2][3];
725  cofact [2][0] = m[3][1];
726  cofact [2][1] = m[3][2];
727  cofact [2][2] = m[3][3];
728  double det = m[0][0] * Det3x3(cofact);
729 
730  cofact [0][0] = m[1][0];
731  cofact [0][1] = m[1][2];
732  cofact [0][2] = m[1][3];
733  cofact [1][0] = m[2][0];
734  cofact [1][1] = m[2][2];
735  cofact [1][2] = m[2][3];
736  cofact [2][0] = m[3][0];
737  cofact [2][1] = m[3][2];
738  cofact [2][2] = m[3][3];
739  det -= m[0][1] * Det3x3(cofact);
740 
741  cofact [0][0] = m[1][0];
742  cofact [0][1] = m[1][1];
743  cofact [0][2] = m[1][3];
744  cofact [1][0] = m[2][0];
745  cofact [1][1] = m[2][1];
746  cofact [1][2] = m[2][3];
747  cofact [2][0] = m[3][0];
748  cofact [2][1] = m[3][1];
749  cofact [2][2] = m[3][3];
750  det += m[0][2] * Det3x3(cofact);
751 
752  cofact [0][0] = m[1][0];
753  cofact [0][1] = m[1][1];
754  cofact [0][2] = m[1][2];
755  cofact [1][0] = m[2][0];
756  cofact [1][1] = m[2][1];
757  cofact [1][2] = m[2][2];
758  cofact [2][0] = m[3][0];
759  cofact [2][1] = m[3][1];
760  cofact [2][2] = m[3][2];
761  det -= m[0][3] * Det3x3(cofact);
762 
763  return det;
764  }
765 
766 
767  // Determinate for 3x3 matrix
768  double ProcessRubberSheet::Det3x3(double m[3][3]) {
769 
770  return m[0][0] * m[1][1] * m[2][2] -
771  m[0][0] * m[1][2] * m[2][1] -
772  m[0][1] * m[1][0] * m[2][2] +
773  m[0][1] * m[1][2] * m[2][0] +
774  m[0][2] * m[1][0] * m[2][1] -
775  m[0][2] * m[1][1] * m[2][0];
776  }
777 
778 
801  void ProcessRubberSheet::processPatchTransform(Transform &trans,
802  Interpolator &interp) {
803 
804  // Error checks ... there must be one input and one output
805  if (InputCubes.size() != 1) {
806  string m = "You must specify exactly one input cube";
807  throw IException(IException::Programmer, m, _FILEINFO_);
808  }
809  else if (OutputCubes.size() != 1) {
810  string m = "You must specify exactly one output cube";
811  throw IException(IException::Programmer, m, _FILEINFO_);
812  }
813 
814  // Create a portal buffer for reading from the input file
815  Portal iportal(interp.Samples(), interp.Lines(),
816  InputCubes[0]->pixelType() ,
817  interp.HotSample(), interp.HotLine());
818 
819  // Setup the progress meter
820  int patchesPerBand = 0;
821  for (int line = m_patchStartLine; line <= InputCubes[0]->lineCount();
822  line += m_patchLineIncrement) {
823  for (int samp = m_patchStartSample;
824  samp <= InputCubes[0]->sampleCount();
825  samp += m_patchSampleIncrement) {
826  patchesPerBand++;
827  }
828  }
829 
830  int patchCount = InputCubes[0]->bandCount() * patchesPerBand;
831  p_progress->SetMaximumSteps(patchCount);
832  p_progress->CheckStatus();
833 
834  // For each band we will loop through the input file and work on small
835  // spatial patches (nxm). The objective is to determine where each patch
836  // falls in the output file and transform that output patch. As we loop
837  // we want some overlap in the input patches which guarantees there will
838  // be no gaps in the output cube.
839  //
840  // We use the variables m_patchLines and m_patchSamples to define
841  // the patch size
842  //
843  // We use the variables m_patchStartLine and m_patchStartSample to define
844  // where to start in the cube. In almost all cases these should be (1,1).
845  // An expection would be for PushFrameCameras which need to have
846  // a different starting line for the even framed cubes
847  //
848  // We use the variables m_patchLineIncrement and m_patchSampleIncrement
849  // to skip to the next patch. Typically these are one less than the
850  // patch size which guarantees a pixel of overlap in the patches. An
851  // exception would be for PushFrameCameras which should increment lines by
852  // twice the framelet height.
853 
854  for (int band=1; band <= InputCubes[0]->bandCount(); band++) {
855  if (p_bandChangeFunct != NULL) p_bandChangeFunct(band);
856  iportal.SetPosition(1,1,band);
857 
858  for (int line = m_patchStartLine; line <= InputCubes[0]->lineCount();
859  line += m_patchLineIncrement) {
860  for (int samp = m_patchStartSample;
861  samp <= InputCubes[0]->sampleCount();
862  samp += m_patchSampleIncrement, p_progress->CheckStatus()) {
863  transformPatch((double)samp, (double)(samp + m_patchSamples - 1),
864  (double)line, (double)(line + m_patchLines - 1),
865  iportal, trans, interp);
866  }
867  }
868  }
869  }
870 
871 
872  // Private method to process a small patch of the input cube
873  void ProcessRubberSheet::transformPatch(double ssamp, double esamp,
874  double sline, double eline,
875  Portal &iportal,
876  Transform &trans,
877  Interpolator &interp) {
878  // Let's make sure our patch is contained in the input file
879  // TODO: Think about the image edges should I be adding 0.5
880  if (esamp > InputCubes[0]->sampleCount()) {
881  esamp = InputCubes[0]->sampleCount();
882  if (ssamp == esamp) ssamp = esamp - 1;
883  }
884  if (eline > InputCubes[0]->lineCount()) {
885  eline = InputCubes[0]->lineCount();
886  if (sline == eline) sline = eline - 1;
887  }
888 
889  // Use these to build a list of four corner control points
890  // If any corner doesn't trasform, split the patch into for smaller patches
891  QVector<double> isamps;
892  QVector<double> ilines;
893  QVector<double> osamps;
894  QVector<double> olines;
895 
896  // Upper left control point
897  double tline, tsamp;
898  if (trans.Xform(tsamp,tline,ssamp,sline)) {
899  isamps.push_back(ssamp);
900  ilines.push_back(sline);
901  osamps.push_back(tsamp);
902  olines.push_back(tline);
903  }
904  else {
905  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
906  return;
907  }
908 
909  // Upper right control point
910  if (trans.Xform(tsamp,tline,esamp,sline)) {
911  isamps.push_back(esamp);
912  ilines.push_back(sline);
913  osamps.push_back(tsamp);
914  olines.push_back(tline);
915  }
916  else {
917  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
918  return;
919  }
920 
921  // Lower left control point
922  if (trans.Xform(tsamp,tline,ssamp,eline)) {
923  isamps.push_back(ssamp);
924  ilines.push_back(eline);
925  osamps.push_back(tsamp);
926  olines.push_back(tline);
927  }
928  else {
929  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
930  return;
931  }
932 
933  // Lower right control point
934  if (trans.Xform(tsamp,tline,esamp,eline)) {
935  isamps.push_back(esamp);
936  ilines.push_back(eline);
937  osamps.push_back(tsamp);
938  olines.push_back(tline);
939  }
940  else {
941  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
942  return;
943  }
944 
945  // Get the min/max output line/sample patch (bounding box of the transformed output samp,line)
946  int osampMin = (int) osamps[0];
947  int osampMax = (int) (osamps[0] + 0.5);
948  int olineMin = (int) olines[0];
949  int olineMax = (int) (olines[0] + 0.5);
950  for (int i = 1; i < osamps.size(); i++) {
951  if (osamps[i] < osampMin) osampMin = (int) osamps[i];
952  if (osamps[i] > osampMax) osampMax = (int) (osamps[i] + 0.5);
953  if (olines[i] < olineMin) olineMin = (int) olines[i];
954  if (olines[i] > olineMax) olineMax = (int) (olines[i] + 0.5);
955  }
956 
957  // Check to see if the output patch is completely outside the image. No
958  // sense in computing the affine if we don't have to.
959  if (osampMax < 1) return;
960  if (olineMax < 1) return;
961  if (osampMin > OutputCubes[0]->sampleCount()) return;
962  if (olineMin > OutputCubes[0]->lineCount()) return;
963 
964  // Adjust our output patch if it extends outside the output cube (overlaps cube boundry)
965  if (osampMin < 1) osampMin = 1;
966  if (olineMin < 1) olineMin = 1;
967  if (osampMax > OutputCubes[0]->sampleCount()) {
968  osampMax = OutputCubes[0]->sampleCount();
969  }
970  if (olineMax > OutputCubes[0]->lineCount()) {
971  olineMax = OutputCubes[0]->lineCount();
972  }
973 
974  /* A small input patch should create a small output patch. If we had
975  * the 0-360 seam (or -180/180) in our patch it could be split across
976  * a cylindrical projection (e.g., equirectangular, simple, etc). So
977  * If the output patch looks like it will span the full output image,
978  * either lines or sample then resplit the input patch. When the patch
979  * spans more than 50% (was 99% but there were problems with double
980  * rounding error on different machines) of the image it is split.
981  */
982 
983  if (osampMax - osampMin + 1.0 > OutputCubes[0]->sampleCount() * 0.50) {
984  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
985  return;
986  }
987  if (olineMax - olineMin + 1.0 > OutputCubes[0]->lineCount() * 0.50) {
988  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
989  return;
990  }
991 
992  // Can we create an affine transform from output to input coordinates
993  BasisFunction isampFunc("Ax+By+C",3,3);
994  LeastSquares isampLSQ(isampFunc);
995 
996  BasisFunction ilineFunc("Dx+Ey+F",3,3);
997  LeastSquares ilineLSQ(ilineFunc);
998 
999  try {
1000  for (int i=0; i < isamps.size(); i++) {
1001  std::vector<double> vars;
1002  vars.push_back(osamps[i]);
1003  vars.push_back(olines[i]);
1004  vars.push_back(1.0);
1005 
1006  isampLSQ.AddKnown(vars,isamps[i]);
1007  ilineLSQ.AddKnown(vars,ilines[i]);
1008  }
1009 
1010  isampLSQ.Solve(LeastSquares::QRD);
1011  ilineLSQ.Solve(LeastSquares::QRD);
1012  }
1013  catch (IException &e) {
1014  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
1015  return;
1016  }
1017 
1018  // If the fit at any corner isn't good enough break it down
1019  for (int i=0; i<isamps.size(); i++) {
1020  if (fabs(isampLSQ.Residual(i)) > 0.5) {
1021  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
1022  return;
1023  }
1024  if (fabs(ilineLSQ.Residual(i)) > 0.5) {
1025  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
1026  return;
1027  }
1028  }
1029 
1030 #if 0
1031  // What about the center point
1032  // TODO: The patches are so small so maybe we don't care if the
1033  // corners worked
1034  double csamp = (ssamp + esamp) / 2.0;
1035  double cline = (sline + eline) / 2.0;
1036  if (trans.Xform(tsamp,tline,csamp,cline)) {
1037  std::vector<double> vars;
1038  vars.push_back(tsamp);
1039  vars.push_back(tline);
1040  vars.push_back(1.0);
1041 
1042  double isamp = isampFunc.Evaluate(vars);
1043  double iline = ilineFunc.Evaluate(vars);
1044 
1045  double err = (csamp - isamp) * (csamp - isamp) +
1046  (cline - iline) * (cline - iline);
1047  if (err > 0.25) {
1048  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
1049  return;
1050  }
1051  }
1052  else {
1053  splitPatch(ssamp, esamp, sline, eline, iportal, trans, interp);
1054  return;
1055  }
1056 #endif
1057 
1058  double A = isampFunc.Coefficient(0);
1059  double B = isampFunc.Coefficient(1);
1060  double C = isampFunc.Coefficient(2);
1061 
1062  double D = ilineFunc.Coefficient(0);
1063  double E = ilineFunc.Coefficient(1);
1064  double F = ilineFunc.Coefficient(2);
1065 
1066  // Now we can do our typical backwards geom. Loop over the output cube
1067  // coordinates and compute input cube coordinates for the corners of the current
1068  // buffer. The buffer is the same size as the current patch size.
1069  Brick oBrick(*OutputCubes[0], osampMax-osampMin+1, olineMax-olineMin+1, 1);
1070  oBrick.SetBasePosition(osampMin, olineMin, iportal.Band());
1071 
1072  int brickIndex = 0;
1073  bool foundNull = false;
1074  for (int oline = olineMin; oline <= olineMax; oline++) {
1075  double isamp = A * osampMin + B * oline + C;
1076  double iline = D * osampMin + E * oline + F;
1077  double isampChangeWRTosamp = A;
1078  double ilineChangeWRTosamp = D;
1079  for (int osamp = osampMin; osamp <= osampMax;
1080  osamp++, isamp += isampChangeWRTosamp, iline += ilineChangeWRTosamp) {
1081  // Now read the data around the input coordinate and interpolate a DN
1082  iportal.SetPosition(isamp, iline, iportal.Band());
1083  InputCubes[0]->read(iportal);
1084  double dn = interp.Interpolate(isamp, iline, iportal.DoubleBuffer());
1085  oBrick[brickIndex] = dn;
1086  if (dn == Null) foundNull = true;
1087  brickIndex++;
1088  }
1089  }
1090 
1091  // If there are any special pixel Null values in this output brick, we may be
1092  // up against an edge of the input image where the interpolaters get Nulls from
1093  // outside the image. Since the patches have some overlap due to finding the
1094  // rectangular area (bounding box, min/max line/samp) of the four points input points
1095  // projected into the output space, this causes valid DNs
1096  // from a previously processed patch to be replaced with Null DNs from this patch.
1097  // NOTE: A different method of accomplishing this fixing of Nulls was tested. We read
1098  // the buffer from the output and tested each pixel values before overwriting it
1099  // This resulted in a slighly slower run for the test. A concern was found in the
1100  // asynchronous write of buffers to the cube, where a race condition may have generated
1101  // different dns, not bad, but making testing more difficult.
1102  if (foundNull) {
1103  Brick readBrick(*OutputCubes[0], osampMax-osampMin+1, olineMax-olineMin+1, 1);
1104  readBrick.SetBasePosition(osampMin, olineMin, iportal.Band());
1105  OutputCubes[0]->read(readBrick);
1106  for (brickIndex = 0; brickIndex<(osampMax-osampMin+1)*(olineMax-olineMin+1); brickIndex++) {
1107  if (readBrick[brickIndex] != Null) {
1108  oBrick[brickIndex] = readBrick[brickIndex];
1109  }
1110  }
1111  }
1112 
1113  // Write filled buffer to cube
1114  OutputCubes[0]->write(oBrick);
1115  }
1116 
1117 
1118  // Private method used to split up input patches if the patch is too big to
1119  // process
1120  void ProcessRubberSheet::splitPatch(double ssamp, double esamp,
1121  double sline, double eline, Portal &iportal,
1122  Transform &trans, Interpolator &interp) {
1123 
1124  // Is the input patch too small to even worry about transforming?
1125  if ((esamp - ssamp < 0.1) && (eline - sline < 0.1)) return;
1126 
1127  // It's big enough so break it into four pieces
1128  double midSamp = (esamp + ssamp) / 2.0;
1129  double midLine = (eline + sline) / 2.0;
1130 
1131  transformPatch(ssamp, midSamp,
1132  sline, midLine,
1133  iportal, trans, interp);
1134  transformPatch(midSamp, esamp,
1135  sline, midLine,
1136  iportal, trans, interp);
1137  transformPatch(ssamp, midSamp,
1138  midLine, eline,
1139  iportal, trans, interp);
1140  transformPatch(midSamp, esamp,
1141  midLine, eline,
1142  iportal, trans, interp);
1143 
1144  return;
1145  }
1146 
1147 
1148 } // end namespace isis
double HotSample()
Returns the sample coordinate of the center pixel in the buffer for the interpolator.
double * DoubleBuffer() const
Returns the value of the shape buffer.
Definition: Buffer.h:154
const double Null
Value for an Isis Null pixel.
Definition: SpecialPixel.h:110
int Band(const int index=0) const
Returns the band position associated with a shape buffer index.
Definition: Buffer.cpp:178
double Interpolate(const double isamp, const double iline, const double buf[])
Performs an interpolation on the data according to the parameters set in the constructor.
Buffer for containing a two dimensional section of an image.
Definition: Portal.h:52
Namespace for the standard library.
virtual bool Xform(double &inSample, double &inLine, const double outSample, const double outLine)=0
Transforms the given output line and sample to the corresponding output line and sample.
This algorithm is designed for applications that jump around between a couple of spots in the cube wi...
int size() const
Returns the total number of pixels in the shape buffer.
Definition: Buffer.h:113
bool begin()
Moves the shape buffer to the first position.
This algorithm is designed for applications that jump around between a couple of spots in the cube wi...
int Tiles()
Returns the number of Tiles in the cube.
Definition: TileManager.h:76
int Sample(const int index=0) const
Returns the sample position associated with a shape buffer index.
Definition: Buffer.cpp:143
double HotLine()
Returns the line coordinate of the center pixel in the buffer for the interpolator.
int Samples()
Returns the number of samples needed by the interpolator.
bool SetTile(const int Tile, const int band=1)
Sets the current tile as requested.
Definition: TileManager.cpp:66
#define _FILEINFO_
Macro for the filename and line number.
Definition: IException.h:40
int Line(const int index=0) const
Returns the line position associated with a shape buffer index.
Definition: Buffer.cpp:161
bool end() const
Returns true if the shape buffer has accessed the end of the cube.
const double E
Sets some basic constants for use in ISIS programming.
Definition: Constants.h:55
void SetPosition(const double sample, const double line, const int band)
Sets the line and sample position of the buffer.
Definition: Portal.h:109
int Lines()
Returns the number of lines needed by the interpolator.
Pixel interpolator.
Definition: Interpolator.h:51
Isis exception class.
Definition: IException.h:107
Namespace for ISIS/Bullet specific routines.
Definition: Apollo.h:31
Pixel transformation.
Definition: Transform.h:89
Buffer manager, for moving through a cube in tiles.
Definition: TileManager.h:55