File: MyListProcessing.m

package info (click to toggle)
lynkeos.app 1.2-6
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 3,924 kB
  • sloc: objc: 7,122; ansic: 695; sh: 372; makefile: 59
file content (759 lines) | stat: -rw-r--r-- 22,894 bytes parent folder | download | duplicates (6)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
//
//  Lynkeos
//  $Id: MyListProcessing.m,v 1.18 2005/02/01 22:59:48 j-etienne Exp $
//
//  Created by Jean-Etienne LAMIAUD on Fri Nov 07 2003.
//  Copyright (c) 2003-2005. Jean-Etienne LAMIAUD
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
//

#include <stdlib.h>
#include <assert.h>

#include "MyListProcessing.h"

#include "fourier.h"
#include "corelation.h"
#include "stack.h"

//==============================================================================
// Generic processing functions
//==============================================================================
static void getImageSample( MyImageListItem* item, RGB *dark, RGB *flat,
                            MyIntegerRect rect, 
                            REAL *spectrum, RGB *pixels, short expand )
{
   NSImage* srcImage;
   NSRect wRect;

   // Keep the original rectangle unchanged
   wRect.origin.x = rect.origin.x;
   wRect.origin.y = rect.origin.y;
   wRect.size.width = rect.size.width;
   wRect.size.height = rect.size.height;

   srcImage = [item getImage];
   if ( srcImage != nil )
   {
      // Create an image to draw the NSImage in
      NSImageRep* srcImageRep = [srcImage bestRepresentationForDevice:nil];
      int width = [srcImageRep pixelsWide],
         height = [srcImageRep pixelsHigh];
      NSImage* offScreenImage = [[[NSImage alloc] initWithSize:NSMakeSize(width,
                                                                        height)]
                                 autorelease];
      NSBitmapImageRep* bitMap;
      u_char *plane[5];
      u_short x, y, w, h, wp, ox, oy, rowSize, pixelSize;
      bool planar;
      RGB color;

      // Draw the desired image in it
      [offScreenImage lockFocus];
      [srcImageRep drawInRect:NSMakeRect(0,0,width,height)];
      // Retrieve the part of the rectangle that is in the image
      if ( wRect.origin.x < 0 )
      {
         wRect.size.width += wRect.origin.x;
         wRect.origin.x = 0;
      }
      if ( wRect.origin.y < 0 )
      {
         wRect.size.height += wRect.origin.y;
         wRect.origin.y = 0;
      }
      if ( wRect.origin.x + wRect.size.width > width )
         wRect.size.width = width - wRect.origin.x;
      if ( wRect.origin.y + wRect.size.height > height )
         wRect.size.height = height - wRect.origin.y;
      bitMap = [[[NSBitmapImageRep alloc] initWithFocusedViewRect:wRect] 
                  autorelease];
      [offScreenImage unlockFocus];

      // Access the data
      [bitMap getBitmapDataPlanes:plane];
      rowSize = [bitMap bytesPerRow];
      pixelSize = [bitMap bitsPerPixel]/8;

      planar = [bitMap isPlanar];
      w = rect.size.width;
      h = rect.size.height;
      ox = wRect.origin.x - rect.origin.x;
      // Beware of the Y flip between bitmap and screen
      oy = rect.origin.y + rect.size.height 
           - wRect.origin.y - wRect.size.height;
      // Padded width for in place transform
      wp = (w/2 + 1)*sizeof(COMPLEX)/sizeof(REAL);

      for ( y = 0; y < h; y++ )
      {
         for ( x = 0; x < w; x++ )
         {
            short i, j;

            // Fill with black outside the image (and take care of the flip, 
            // again and again...)
            if ( rect.origin.x + x < 0 || 
                 rect.origin.x + x >= wRect.origin.x + wRect.size.width ||
                 rect.origin.y + h - 1 - y < 0 || 
                 rect.origin.y + h - 1 - y >= 
                                            wRect.origin.y + wRect.size.height )
            {
               color.red = 0;
               color.green = 0;
               color.blue = 0;
            }
            else
            {
               // Read the data in the bitmap
               if ( planar )
               {
                  color.red = plane[0][(y-oy)*rowSize+x-ox];
                  color.green = plane[1][(y-oy)*rowSize+x-ox];
                  color.blue = plane[2][(y-oy)*rowSize+x-ox];
               }
               else
               {
                  color.red = plane[0][(y-oy)*rowSize+(x-ox)*pixelSize];
                  color.green = plane[0][(y-oy)*rowSize+(x-ox)*pixelSize+1];
                  color.blue = plane[0][(y-oy)*rowSize+(x-ox)*pixelSize+2];
               }

               // Apply dark frame and flat field if any
               if ( dark != NULL )
               {
                  RGB darkColor = dark[(height-rect.origin.y-h+1 + y)*width 
                                       + rect.origin.x + x];
                  color.red -= darkColor.red;
                  color.green -= darkColor.green;
                  color.blue -= darkColor.blue;
               }

               if ( flat != NULL )
               {
                  RGB flatColor = flat[(height-rect.origin.y-h+1 + y)*width 
                                       + rect.origin.x + x];
                  // No check for too small value
                  // The user will soon see if the flat field is bad
                  color.red /= flatColor.red;
                  color.green /= flatColor.green;
                  color.blue /= flatColor.blue;
               }
            }

            // Fill in the spectrum
            if ( spectrum != NULL )
               spectrum[y*wp+x] = (color.red + color.green + color.blue) / 3.0;

            // Expand the cropped part
            if ( pixels != NULL )
            {
               for ( j = 0; j < expand; j++ )
                  for ( i = 0; i < expand; i++ )
                     pixels[(y*expand + j)*w*expand + x*expand + i] = color;
            }
         }
      }

   }
}

// Cut the highest frequencies from the spectrum to suppress noise
static void cutoffSpectrum( SPECTRUM spectrum, u_short width, u_short height, 
                            u_short cutoff )
{
   u_short x, y;
   u_short h_2 = height/2;
   u_long cut2 = cutoff*cutoff;

   // Save time if there is no cutoff at all
   if ( cutoff >= sqrt(width*width+height*height) )
      return;

   for ( y = 0; y < height; y++ )
   {
      for ( x = 0; x < width/2 + 1; x++ )
      {
         short dx = x, dy = y;
         u_long f2; 
         if ( dy >= h_2 )
            dy -= height;
         f2 = dx*dx + dy*dy;

         if ( f2 > cut2 )
            spectrum[y*(width/2+1)+x] = 0.0;
      }
   }
}

static double quality( SPECTRUM spectrum, u_short width, u_short height,
                       u_short down, u_short up )
{
    u_short x, y;
    double q = 0.0;
    u_long d2 = down*down, u2 = up*up;
    u_long n = 0;
    double lum = (__real__ spectrum[0])/(double)width/(double)height;

    for( y = 0; y < height; y++ )
    {
        for ( x = 0; x < width/2 +1; x++ )
        {
            short dx = x, dy = y;
            long f2;
            if ( dy >= height/2 )
                dy -= height;
            f2 = dx*dx + dy*dy;
            if ( f2 > d2 && f2 < u2 )
            {
                COMPLEX s = spectrum[y*(width/2+1)+x];
                q += sqrt( __real__ s * __real__ s + __imag__ s * __imag__ s );
                n++;
            }
        }
    }

    return( q/lum/(double)n );
}

static double entropy( REAL *image, u_short width, u_short height )
{
   // Padded width for in place transform
   u_short pw = (width/2+1)*sizeof(COMPLEX)/sizeof(REAL);
   double e = 0, bmax = 0;
   u_long x, y;

   // Compute the quadratic pixel sum
   for( y = 0; y < height; y++ )
      for( x = 0; x < width; x++ )
         bmax += image[y*pw+x] * image[y*pw+x];

   bmax = sqrt(bmax);

   // Compute the entropy
   for( y = 0; y < height; y++ )
   {
      for( x = 0; x < width; x++ )
      {
         double b = image[y*pw+x]/bmax;
         if ( b > 0.0 )
            e -= b * log(b);
      }
   }

   return( e );
}

//==============================================================================
// Private common part of list processing
//==============================================================================
@interface MyListProcessing(Private)
- (id) initWithDelegate :(id)delegate ;
- (void) setEnumerator:(NSData*)list 
             darkFrame:(NSData*)dark flatField:(NSData*)flat ;
- (void) processList ;
- (void) processNextItem ;
- (void) processItem :(MyImageListItem*)item ; // To be overriden in subclasses
@end

@implementation MyListProcessing(Private)

- (id) initWithDelegate :(id)delegate
{
   [self init];

   _result = nil;
   _delegate = delegate;
   _processEnded = NO;
   _list = nil;
   _cropRectangle = MyMakeIntegerRect(0,0,0,0);

   [_delegate processDidCreate:self];

   return( self );
}

- (void) setEnumerator:(NSData*)list 
             darkFrame:(NSData*)dark flatField:(NSData*)flat
{
   // No need to retain, it is not autoreleased in this thread
   [list getBytes:&_list];

   [dark getBytes:&_darkFrame];
   [flat getBytes:&_flatField];
}

- (void) processList
{
   // Create a run loop for this thread
   NSRunLoop* runLoop = [NSRunLoop currentRunLoop];

   while ( ! _processEnded )
   {
      // Process the run loop to handle inter-threads messaging
      if ( _list == nil )
         // Infinite timeout when there is no list to process yet
         [runLoop runMode: NSDefaultRunLoopMode 
                  beforeDate:[NSDate distantFuture]];
      else
      {
         // Null timeout and process next item immediately after
         [runLoop runMode :NSDefaultRunLoopMode beforeDate:[NSDate date]];
         if ( ! _processEnded  )
            [self processNextItem];
      }
   }

   [_delegate processDidFinish:self data:_result];
}

- (void) processNextItem
{
   NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
   MyImageListItem *item = [_list nextObject];

   if ( item == nil )
      // Process is finished
      [self stopProcessing];
   else
      [self processItem:item];

   [pool release];
}

- (void) processItem :(MyImageListItem*)item
{
   NSAssert( NO, @"MyListProcessing doesn't respond to processItem" );
}

@end

//==============================================================================
// Root class for list processing
//==============================================================================
@implementation MyListProcessing

- (void) dealloc
{
   [_result release];
   [super dealloc];
}

+ (void) threadWithAttributes :(NSDictionary*)attr 
{
   NSAutoreleasePool *pool;
   NSPort *rxPort, *txPort;
   NSConnection *cnx;
   MyListProcessing *process;
   id delegate;

   pool = [[NSAutoreleasePool alloc] init];

   rxPort = [attr objectForKey:K_RX_PORT_KEY];
   txPort = [attr objectForKey:K_TX_PORT_KEY];
   NSAssert( rxPort != nil && txPort != nil, 
             @"Missing connection for MyListProcessing thread" );

   cnx = [NSConnection connectionWithReceivePort:rxPort sendPort:txPort];

   delegate = [cnx rootProxy];
   process = [[self alloc] initWithDelegate:delegate];
   // The main thread has sent retain to "process" proxy, but it seems to retain
   // our "process" also, while when the main threads sends "release" to the 
   // proxy, it will not release our "process", therefore we correct the retain 
   // count :
   [process release];

   [process processList];

   // As the "release" in the main thread is sent only to the proxy "process"
   // This is the real release for this thread
   [process release];

   [pool release];
}

- (void) stopProcessing
{
   _processEnded = YES;
}

@end

//==============================================================================
// Image aligner derived class
//==============================================================================
@implementation MyImageAligner

- (id) init
{
   if ( (self = [super init]) != nil )
   {
      _referenceItem = nil;
      FFT_DATA_INIT( &_bufferSpectrum );
      _referenceSpectrum = nil;
      _referenceOrigin = MyMakeIntegerPoint(0,0);
      _side = 0;
      _cutoff = 0;
      _precisionThreshold = 0;
      _refSpectrumLock = nil;
      _refSpectrumAvailable = NO;
   }

   return( self );
}

- (void) dealloc
{
   free_spectrum( &_bufferSpectrum );

   [super dealloc];
}

- (oneway void) alignWithList :(NSData*)list reference:(NSData*)refItem 
                     refBuffer:(NSData*)refSpectrum
                          lock:(NSData*)lock
                    darkFrame:(NSData*)dark flatField:(NSData*)flat
                     rectangle:(MyIntegerRect)rect 
                        cutOff:(double)cutoff 
            precisionThreshold:(double)threshold
{
   NSPoint offset;
   MyIntegerRect r;

   [self setEnumerator:list darkFrame:dark flatField:flat];
   _cropRectangle = rect;

   [lock getBytes:&_refSpectrumLock];
   
   [refItem getBytes:&_referenceItem];
   NSAssert( _referenceItem != nil, @"No reference item to align from" );
   [_referenceItem retain];

   _side = _cropRectangle.size.width ;

   // Get the other attributes
   _cutoff = _side*cutoff;
   _precisionThreshold = _side*threshold;
   [refSpectrum getBytes :&_referenceSpectrum];

   // Extract the data
   r = _cropRectangle;
   if ( [_referenceItem hasSearchSquare] )
      r.origin = [_referenceItem searchSquareOrigin];
   _referenceOrigin = r.origin;

   // Prepare the reference spectrum in only one thread
   if ( [_refSpectrumLock tryLock] )
   {
      getImageSample( _referenceItem, _darkFrame, _flatField, r, 
                      (REAL*)_referenceSpectrum->spectrum, nil, 1 );

      // And transform it into a spectrum
      fourier( *_referenceSpectrum );

      // Cut the highest frequencies
      cutoffSpectrum( _referenceSpectrum->spectrum, _side, _side, _cutoff );

      // Set the reference item to 0,0 offset
      offset.x = 0.0;
      offset.y = 0.0;
      [_delegate processDidProgress:[NSData dataWithBytes:&_referenceItem 
                                            length:sizeof(MyImageListItem*)]
                                            data:[NSData dataWithBytes:&offset 
                                            length:sizeof(NSPoint)]];
      _refSpectrumAvailable = YES;
      [_refSpectrumLock unlock];
   }

   // Allocate the buffer for each other images
   allocate_spectrum( &_bufferSpectrum, _side, _side, 1, 
                      FOR_DIRECT|FOR_INVERSE );
}

- (void) processItem :(MyImageListItem*)item
{
   if ( item != _referenceItem && [item getSelectionState] == NSOnState )
   {
      MyIntegerRect r = _cropRectangle;
      NSPoint offset;
      CORRELATION_PEAK peak;

      // Get the spectrum of that other image
      if ( [item hasSearchSquare] )
         r.origin = [item searchSquareOrigin];
      getImageSample( item, _darkFrame, _flatField, r, 
                      (REAL*)_bufferSpectrum.spectrum, nil, 1 );
      fourier( _bufferSpectrum );
      cutoffSpectrum( _bufferSpectrum.spectrum, _side, _side, _cutoff );

      // Check the reference spectrum availability before corelating against it
      if ( !_refSpectrumAvailable )
      {
         // Rendez vous with the "reference" thread
         [_refSpectrumLock lock];
         _refSpectrumAvailable = YES;
         [_refSpectrumLock unlock];
      }

      // correlate it against the reference
      correlate_spectrums( *_referenceSpectrum, _bufferSpectrum, 
                           _bufferSpectrum );
      corelation_peak( _bufferSpectrum, &peak );

      if ( peak.sigma_x < _precisionThreshold 
           && peak.sigma_y < _precisionThreshold )
      {
         // Beware, there is a y-flip between the bitmap and the screen
         offset.x = peak.x - r.origin.x + _referenceOrigin.x;
         offset.y = -peak.y - r.origin.y + _referenceOrigin.y;

         // Inform the delegate
         [_delegate processDidProgress:[NSData dataWithBytes:&item 
                                               length:sizeof(MyImageListItem*)]
                                  data:[NSData dataWithBytes:&offset 
                                               length:sizeof(NSPoint)]];
      }
      else{
         // Inform the delegate with no alignment
         [_delegate processDidProgress:[NSData dataWithBytes:&item 
                                               length:sizeof(MyImageListItem*)]
                                  data:nil];
      }
   }
}

@end

//==============================================================================
// Image analyzer derived class
//==============================================================================
@implementation MyImageAnalyzer

- (id) init
{
   [super init];

   _side = 0;
   _lowerCutoff = 0;
   _upperCutoff = 0;
   FFT_DATA_INIT( &_bufferSpectrum );

   return( self );
}

- (void) dealloc
{
   free_spectrum( &_bufferSpectrum );
   [super dealloc];
}

- (oneway void) analyzeWithList :(NSData*)list 
                       darkFrame:(NSData*)dark flatField:(NSData*)flat
                       rectangle:(MyIntegerRect)rect
                          method:(MyAnalysisMethod)method
                       lowCutoff:(double)lCutoff highCutoff:(double)hCutoff
{
   [self setEnumerator:list darkFrame:dark flatField:flat];
   _cropRectangle = rect;

   _side = _cropRectangle.size.width ;

   _method = method;
   _lowerCutoff = _side*lCutoff;
   _upperCutoff= _side*hCutoff;

   // Allocate the buffer for each image
   allocate_spectrum( &_bufferSpectrum, _side, _side, 1,
                      _method == SpectrumAnalysis ? FOR_DIRECT : 0 );
}

- (void) processItem :(MyImageListItem*)item
{
   if ( [item getSelectionState] == NSOnState )
   {
      MyIntegerRect r = _cropRectangle;
      double q;

      // Get the spectrum of that image
      if ( [item hasSearchSquare] )
         r.origin = [item searchSquareOrigin];
      getImageSample( item, _darkFrame, _flatField, r, 
                      (REAL*)_bufferSpectrum.spectrum, NULL, 1 );
      if ( _method == SpectrumAnalysis )
          fourier( _bufferSpectrum );

      // Analyze its quality
      switch ( _method )
      {
         case SpectrumAnalysis:
          q = quality( _bufferSpectrum.spectrum, _side, _side, 
                       _lowerCutoff, _upperCutoff );
            break;
         case EntropyAnalysis:
            // Maximum entropy of N pixels is sqrt(N)*log(sqrt(N))
            q = (_side*log(_side) / 
                 entropy((REAL*)_bufferSpectrum.spectrum, _side, _side) 
                  -  1.0) * 10.0;
            break;
         default:
            NSAssert(NO, @"Invalid analysis method");
      }

      if ( isnan( q ) )
         printf( "NaN quality !\n" );
      // Inform the delegate
      [_delegate processDidProgress:[NSData dataWithBytes:&item 
                                            length:sizeof(MyImageListItem*)]
                               data:[NSData dataWithBytes:&q 
                                            length:sizeof(double)]];
   }
}

@end

//==============================================================================
// Image stacker derived class
//==============================================================================
@implementation MyImageStack

- (id) init
{
   [super init];

   _rgbSum = NULL;
   _rgbBuffer = NULL;
   _factor = 0;

   return( self );
}

- (void) dealloc
{
   if ( _rgbBuffer != NULL )
      free( _rgbBuffer );

   [super dealloc];
}

- (oneway void) stackWithList:(NSData*)list 
                    darkFrame:(NSData*)dark flatField:(NSData*)flat
                    rectangle:(MyIntegerRect)rect
                   sizeFactor:(u_short)factor
{
   const RGB Black = {0.0,0.0,0.0};
   u_short x, y;

   [self setEnumerator:list darkFrame:dark flatField:flat];
   _cropRectangle = rect;
   _factor = factor;

   // Prepare an empty image to stack in
   _rgbSum = (RGB*)malloc( _cropRectangle.size.width*_factor
                           * _cropRectangle.size.height*_factor
                           * sizeof(RGB) );
   for ( y = 0; y < _cropRectangle.size.height*_factor; y++ )
      for ( x = 0; x < _cropRectangle.size.width*_factor; x++ )
         _rgbSum[y*_cropRectangle.size.width*_factor+x] = Black;

   _result = [[NSData dataWithBytes:&_rgbSum length:sizeof(RGB*)] retain];

   _rgbBuffer = (RGB*)malloc( _cropRectangle.size.width*_factor
                           * _cropRectangle.size.height*_factor
                           * sizeof(RGB) );
}

- (void) processItem :(MyImageListItem*)item
{
   if ( [item getSelectionState] == NSOnState && [item isAligned] )
   {
      MyIntegerRect r = _cropRectangle;
      MyIntegerPoint shift;
      NSPoint p = [item alignOffset];

      // Get the image part to add
      p.x *= -1;	// Shift the crop rectangle in the opposite side
      p.y *= -1;
      shift.x = (p.x < 0 ? (int)(p.x-1) : (int)p.x);   // Crop at integer pixels
      shift.y = (p.y < 0 ? (int)(p.y-1) : (int)p.y);
      r.origin.x += shift.x;
      r.origin.y += shift.y;
      getImageSample( item, _darkFrame, _flatField, r, nil, _rgbBuffer, 
                      _factor );

      // Accumulate (Warning, there is a Y flip between screen and bitmap)
      stack_layer( _rgbSum, _rgbBuffer, (p.x - shift.x)*(double)_factor, 
                   (1.0 - p.y + shift.y)*(double)_factor,
                   r.size.width*_factor, r.size.height*_factor );

      // Inform the delegate
      [_delegate processDidProgress:[NSData dataWithBytes:&item 
                                            length:sizeof(MyImageListItem*)]
                               data:nil];
   }
}

@end

void normalize_rgb( RGB *rgb, u_long length, double scale, BOOL mono )
{
   double s;
   u_long i;

   // Calculate max value if needed
   if ( scale == 0.0 )
   {
      double max = 0;

      // The scale shall be 1/max
      for( i = 0; i < length; i++ )
      {
         RGB v = rgb[i];

         assert( v.red >= 0 && v.green >= 0 && v.blue >= 0 );

         // Make the image monochromatic if needed
         if ( mono )
         {
            double m = (v.red + v.green + v.blue)/3.0;

            v.red = m;
            v.green = m;
            v.blue = m;
            rgb[i] = v;
         }

         if ( v.red > max )
            max = v.red;
         if ( v.green > max )
            max = v.green;
         if ( v.blue > max )
            max = v.blue;
      }
      s = 1.0/max;
   }
   else
      s = scale;

   // Apply the scale
   for( i = 0; i < length; i++ )
   {
      rgb[i].red *= s;
      rgb[i].green *= s;
      rgb[i].blue *= s;
   }
}