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