USGS

Isis 3.0 Object Programmers' Reference

Home

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