• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**********************************************************************
2  * File:        charsample.cpp  (Formerly charsample.c)
3  * Description: Class to contain character samples and match scores
4  *					to be used for adaption
5  * Author:      Chris Newton
6  * Created:     Thu Oct  7 13:40:37 BST 1993
7  *
8  * (C) Copyright 1993, Hewlett-Packard Ltd.
9  ** Licensed under the Apache License, Version 2.0 (the "License");
10  ** you may not use this file except in compliance with the License.
11  ** You may obtain a copy of the License at
12  ** http://www.apache.org/licenses/LICENSE-2.0
13  ** Unless required by applicable law or agreed to in writing, software
14  ** distributed under the License is distributed on an "AS IS" BASIS,
15  ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  ** See the License for the specific language governing permissions and
17  ** limitations under the License.
18  *
19  **********************************************************************/
20 
21 #include "mfcpch.h"
22 #include <stdio.h>
23 #include          <ctype.h>
24 #include          <math.h>
25 #ifdef __UNIX__
26 #include <assert.h>
27 #include          <unistd.h>
28 #endif
29 #include "memry.h"
30 #include          "tessvars.h"
31 #include "statistc.h"
32 #include          "charsample.h"
33 #include "paircmp.h"
34 #include "matmatch.h"
35 #include          "adaptions.h"
36 #include          "secname.h"
37 #include          "notdll.h"
38 #include          "tesseractclass.h"
39 
40 extern inT32 demo_word;          // Hack for demos
41 
ELISTIZE(CHAR_SAMPLES)42 ELISTIZE (CHAR_SAMPLE) ELISTIZE (CHAR_SAMPLES) CHAR_SAMPLE::CHAR_SAMPLE () {
43   sample_blob = NULL;
44   sample_denorm = NULL;
45   sample_image = NULL;
46   ch = '\0';
47   n_samples_matched = 0;
48   total_match_scores = 0.0;
49   sumsq_match_scores = 0.0;
50 }
51 
52 
CHAR_SAMPLE(PBLOB * blob,DENORM * denorm,char c)53 CHAR_SAMPLE::CHAR_SAMPLE(PBLOB *blob, DENORM *denorm, char c) {
54   sample_blob = blob;
55   sample_denorm = denorm;
56   sample_image = NULL;
57   ch = c;
58   n_samples_matched = 0;
59   total_match_scores = 0.0;
60   sumsq_match_scores = 0.0;
61 }
62 
63 
CHAR_SAMPLE(IMAGE * image,char c)64 CHAR_SAMPLE::CHAR_SAMPLE(IMAGE *image, char c) {
65   sample_blob = NULL;
66   sample_denorm = NULL;
67   sample_image = image;
68   ch = c;
69   n_samples_matched = 0;
70   total_match_scores = 0.0;
71   sumsq_match_scores = 0.0;
72 }
73 
74 
match_sample(CHAR_SAMPLE * test_sample,BOOL8 updating,tesseract::Tesseract * tess)75 float CHAR_SAMPLE::match_sample(  // Update match scores
76                                 CHAR_SAMPLE *test_sample,
77                                 BOOL8 updating,
78                                 tesseract::Tesseract* tess) {
79   float score1;
80   float score2;
81   IMAGE *image = test_sample->image ();
82 
83   if (sample_blob != NULL && test_sample->blob () != NULL) {
84     PBLOB *blob = test_sample->blob ();
85     DENORM *denorm = test_sample->denorm ();
86 
87     score1 = tess->compare_bln_blobs (sample_blob, sample_denorm, blob, denorm);
88     score2 = tess->compare_bln_blobs (blob, denorm, sample_blob, sample_denorm);
89 
90     score1 = (score1 > score2) ? score1 : score2;
91   }
92   else if (sample_image != NULL && image != NULL) {
93     CHAR_PROTO *sample = new CHAR_PROTO (this);
94 
95     score1 = matrix_match (sample_image, image);
96     delete sample;
97   }
98   else
99     return BAD_SCORE;
100 
101   if ((tessedit_use_best_sample || tessedit_cluster_debug) && updating) {
102     n_samples_matched++;
103     total_match_scores += score1;
104     sumsq_match_scores += score1 * score1;
105   }
106   return score1;
107 }
108 
109 
mean_score()110 double CHAR_SAMPLE::mean_score() {
111   if (n_samples_matched > 0)
112     return (total_match_scores / n_samples_matched);
113   else
114     return BAD_SCORE;
115 }
116 
117 
variance()118 double CHAR_SAMPLE::variance() {
119   double mean = mean_score ();
120 
121   if (n_samples_matched > 0) {
122     return (sumsq_match_scores / n_samples_matched) - mean * mean;
123   }
124   else
125     return BAD_SCORE;
126 }
127 
128 
print(FILE * f)129 void CHAR_SAMPLE::print(FILE *f) {
130   if (!tessedit_cluster_debug)
131     return;
132 
133   if (n_samples_matched > 0)
134     fprintf (f,
135       "%c - sample matched against " INT32FORMAT
136       " blobs, mean: %f, var: %f\n", ch, n_samples_matched,
137       mean_score (), variance ());
138   else
139     fprintf (f, "No matches for this sample (%c)\n", ch);
140 }
141 
142 
reset_match_statistics()143 void CHAR_SAMPLE::reset_match_statistics() {
144   n_samples_matched = 0;
145   total_match_scores = 0.0;
146   sumsq_match_scores = 0.0;
147 }
148 
149 
CHAR_SAMPLES()150 CHAR_SAMPLES::CHAR_SAMPLES() {
151   type = UNKNOWN;
152   samples.clear ();
153   ch = '\0';
154   best_sample = NULL;
155   proto = NULL;
156 }
157 
158 
CHAR_SAMPLES(CHAR_SAMPLE * sample)159 CHAR_SAMPLES::CHAR_SAMPLES(CHAR_SAMPLE *sample) {
160   CHAR_SAMPLE_IT sample_it = &samples;
161 
162   ASSERT_HOST (sample->image () != NULL || sample->blob () != NULL);
163 
164   if (sample->image () != NULL)
165     type = IMAGE_CLUSTER;
166   else if (sample->blob () != NULL)
167     type = BLOB_CLUSTER;
168 
169   samples.clear ();
170   sample_it.add_to_end (sample);
171   if (tessedit_mm_only_match_same_char)
172     ch = sample->character ();
173   else
174     ch = '\0';
175   best_sample = NULL;
176   proto = NULL;
177 }
178 
179 
add_sample(CHAR_SAMPLE * sample,tesseract::Tesseract * tess)180 void CHAR_SAMPLES::add_sample(CHAR_SAMPLE *sample, tesseract::Tesseract* tess) {
181   CHAR_SAMPLE_IT sample_it = &samples;
182 
183   if (tessedit_use_best_sample || tessedit_cluster_debug)
184     for (sample_it.mark_cycle_pt ();
185   !sample_it.cycled_list (); sample_it.forward ()) {
186     sample_it.data ()->match_sample (sample, TRUE, tess);
187     sample->match_sample (sample_it.data (), TRUE, tess);
188   }
189 
190   sample_it.add_to_end (sample);
191 
192   if (tessedit_mm_use_prototypes && type == IMAGE_CLUSTER) {
193     if (samples.length () == tessedit_mm_prototype_min_size)
194       this->build_prototype ();
195     else if (samples.length () > tessedit_mm_prototype_min_size)
196       this->add_sample_to_prototype (sample);
197   }
198 }
199 
200 
add_sample_to_prototype(CHAR_SAMPLE * sample)201 void CHAR_SAMPLES::add_sample_to_prototype(CHAR_SAMPLE *sample) {
202   BOOL8 rebuild = FALSE;
203   inT32 new_xsize = proto->x_size ();
204   inT32 new_ysize = proto->y_size ();
205   inT32 sample_xsize = sample->image ()->get_xsize ();
206   inT32 sample_ysize = sample->image ()->get_ysize ();
207 
208   if (sample_xsize > new_xsize) {
209     new_xsize = sample_xsize;
210     rebuild = TRUE;
211   }
212   if (sample_ysize > new_ysize) {
213     new_ysize = sample_ysize;
214     rebuild = TRUE;
215   }
216 
217   if (rebuild)
218     proto->enlarge_prototype (new_xsize, new_ysize);
219 
220   proto->add_sample (sample);
221 }
222 
223 
build_prototype()224 void CHAR_SAMPLES::build_prototype() {
225   CHAR_SAMPLE_IT sample_it = &samples;
226   CHAR_SAMPLE *sample;
227   inT32 proto_xsize = 0;
228   inT32 proto_ysize = 0;
229 
230   if (type != IMAGE_CLUSTER
231     || samples.length () < tessedit_mm_prototype_min_size)
232     return;
233 
234   for (sample_it.mark_cycle_pt ();
235   !sample_it.cycled_list (); sample_it.forward ()) {
236     sample = sample_it.data ();
237     if (sample->image ()->get_xsize () > proto_xsize)
238       proto_xsize = sample->image ()->get_xsize ();
239     if (sample->image ()->get_ysize () > proto_ysize)
240       proto_ysize = sample->image ()->get_ysize ();
241   }
242 
243   proto = new CHAR_PROTO (proto_xsize, proto_ysize, 0, 0, '\0');
244 
245   for (sample_it.mark_cycle_pt ();
246     !sample_it.cycled_list (); sample_it.forward ())
247   this->add_sample_to_prototype (sample_it.data ());
248 
249 }
250 
251 
find_best_sample()252 void CHAR_SAMPLES::find_best_sample() {
253   CHAR_SAMPLE_IT sample_it = &samples;
254   double score;
255   double best_score = MAX_INT32;
256 
257   if (ch == '\0' || samples.length () < tessedit_mm_prototype_min_size)
258     return;
259 
260   for (sample_it.mark_cycle_pt ();
261   !sample_it.cycled_list (); sample_it.forward ()) {
262     score = sample_it.data ()->mean_score ();
263     if (score < best_score) {
264       best_score = score;
265       best_sample = sample_it.data ();
266     }
267   }
268   #ifndef SECURE_NAMES
269   if (tessedit_cluster_debug) {
270     tprintf ("Best sample for this %c cluster:\n", ch);
271     best_sample->print (debug_fp);
272   }
273   #endif
274 }
275 
276 
match_score(CHAR_SAMPLE * sample,tesseract::Tesseract * tess)277 float CHAR_SAMPLES::match_score(CHAR_SAMPLE *sample,
278                                 tesseract::Tesseract* tess) {
279   if (tessedit_mm_only_match_same_char && sample->character () != ch)
280     return BAD_SCORE;
281 
282   if (tessedit_use_best_sample && best_sample != NULL)
283     return best_sample->match_sample (sample, FALSE, tess);
284   else if ((tessedit_mm_use_prototypes
285     || tessedit_mm_adapt_using_prototypes) && proto != NULL)
286     return proto->match_sample (sample);
287   else
288     return this->nn_match_score (sample, tess);
289 }
290 
291 
nn_match_score(CHAR_SAMPLE * sample,tesseract::Tesseract * tess)292 float CHAR_SAMPLES::nn_match_score(CHAR_SAMPLE *sample,
293                                    tesseract::Tesseract* tess) {
294   CHAR_SAMPLE_IT sample_it = &samples;
295   float score;
296   float min_score = MAX_INT32;
297 
298   for (sample_it.mark_cycle_pt ();
299   !sample_it.cycled_list (); sample_it.forward ()) {
300     score = sample_it.data ()->match_sample (sample, FALSE, tess);
301     if (score < min_score)
302       min_score = score;
303   }
304 
305   return min_score;
306 }
307 
308 
assign_to_char()309 void CHAR_SAMPLES::assign_to_char() {
310   STATS char_frequency(FIRST_CHAR, LAST_CHAR);
311   CHAR_SAMPLE_IT sample_it = &samples;
312   inT32 i;
313   inT32 max_index = 0;
314   inT32 max_freq = 0;
315 
316   if (samples.length () == 0 || tessedit_mm_only_match_same_char)
317     return;
318 
319   for (sample_it.mark_cycle_pt ();
320     !sample_it.cycled_list (); sample_it.forward ())
321   char_frequency.add ((inT32) sample_it.data ()->character (), 1);
322 
323   for (i = FIRST_CHAR; i <= LAST_CHAR; i++)
324   if (char_frequency.pile_count (i) > max_freq) {
325     max_index = i;
326     max_freq = char_frequency.pile_count (i);
327   }
328 
329   if (samples.length () >= tessedit_cluster_min_size
330     && max_freq > samples.length () * tessedit_cluster_accept_fraction)
331     ch = (char) max_index;
332 }
333 
334 
print(FILE * f)335 void CHAR_SAMPLES::print(FILE *f) {
336   CHAR_SAMPLE_IT sample_it = &samples;
337 
338   fprintf (f, "Collected " INT32FORMAT " samples\n", samples.length ());
339 
340   #ifndef SECURE_NAMES
341   if (tessedit_cluster_debug)
342     for (sample_it.mark_cycle_pt ();
343     !sample_it.cycled_list (); sample_it.forward ())
344   sample_it.data ()->print (f);
345 
346   if (ch == '\0')
347     fprintf (f, "\nCluster not used for adaption\n");
348   else
349     fprintf (f, "\nCluster used to adapt to '%c's\n", ch);
350   #endif
351 }
352 
353 
CHAR_PROTO()354 CHAR_PROTO::CHAR_PROTO() {
355   xsize = 0;
356   ysize = 0;
357   ch = '\0';
358   nsamples = 0;
359   proto_data = NULL;
360   proto = NULL;
361 }
362 
363 
CHAR_PROTO(inT32 x_size,inT32 y_size,inT32 n_samples,float initial_value,char c)364 CHAR_PROTO::CHAR_PROTO(inT32 x_size,
365                        inT32 y_size,
366                        inT32 n_samples,
367                        float initial_value,
368                        char c) {
369   inT32 x;
370   inT32 y;
371 
372   xsize = x_size;
373   ysize = y_size;
374   ch = c;
375   nsamples = n_samples;
376 
377   ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);
378 
379   for (y = 0; y < ysize; y++)
380     for (x = 0; x < xsize; x++)
381       proto[x][y] = initial_value;
382 }
383 
384 
CHAR_PROTO(CHAR_SAMPLE * sample)385 CHAR_PROTO::CHAR_PROTO(CHAR_SAMPLE *sample) {
386   inT32 x;
387   inT32 y;
388   IMAGELINE imline_s;
389 
390   if (sample->image () == NULL) {
391     xsize = 0;
392     ysize = 0;
393     ch = '\0';
394     nsamples = 0;
395     proto_data = NULL;
396     proto = NULL;
397   }
398   else {
399     ch = sample->character ();
400     xsize = sample->image ()->get_xsize ();
401     ysize = sample->image ()->get_ysize ();
402     nsamples = 1;
403 
404     ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);
405 
406     for (y = 0; y < ysize; y++) {
407       sample->image ()->fast_get_line (0, y, xsize, &imline_s);
408       for (x = 0; x < xsize; x++)
409         if (imline_s.pixels[x] == BINIM_WHITE)
410           proto[x][y] = 1.0;
411       else
412         proto[x][y] = -1.0;
413     }
414   }
415 }
416 
417 
~CHAR_PROTO()418 CHAR_PROTO::~CHAR_PROTO () {
419   if (proto_data != NULL)
420     FREE_2D_ARRAY(proto_data, proto);
421 }
422 
423 
match_sample(CHAR_SAMPLE * test_sample)424 float CHAR_PROTO::match_sample(CHAR_SAMPLE *test_sample) {
425   CHAR_PROTO *test_proto;
426   float score;
427 
428   if (test_sample->image () != NULL) {
429     test_proto = new CHAR_PROTO (test_sample);
430     if (xsize > test_proto->x_size ())
431       score = this->match (test_proto);
432     else {
433       demo_word = -demo_word;    // Flag different call
434       score = test_proto->match (this);
435     }
436   }
437   else
438     return BAD_SCORE;
439 
440   delete test_proto;
441 
442   return score;
443 }
444 
445 
match(CHAR_PROTO * test_proto)446 float CHAR_PROTO::match(CHAR_PROTO *test_proto) {
447   inT32 xsize2 = test_proto->x_size ();
448   inT32 y_size;
449   inT32 y_size2;
450   inT32 x_offset;
451   inT32 y_offset;
452   inT32 x;
453   inT32 y;
454   CHAR_PROTO *match_proto;
455   float score;
456   float sum = 0.0;
457 
458   ASSERT_HOST (xsize >= xsize2);
459 
460   x_offset = (xsize - xsize2) / 2;
461 
462   if (ysize < test_proto->y_size ()) {
463     y_size = test_proto->y_size ();
464     y_size2 = ysize;
465     y_offset = (y_size - y_size2) / 2;
466 
467     match_proto = new CHAR_PROTO (xsize,
468       y_size,
469       nsamples * test_proto->n_samples (),
470       0, '\0');
471 
472     for (y = 0; y < y_offset; y++) {
473       for (x = 0; x < xsize2; x++) {
474         match_proto->data ()[x + x_offset][y] =
475           test_proto->data ()[x][y] * nsamples;
476         sum += match_proto->data ()[x + x_offset][y];
477       }
478     }
479 
480     for (y = y_offset + y_size2; y < y_size; y++) {
481       for (x = 0; x < xsize2; x++) {
482         match_proto->data ()[x + x_offset][y] =
483           test_proto->data ()[x][y] * nsamples;
484         sum += match_proto->data ()[x + x_offset][y];
485       }
486     }
487 
488     for (y = y_offset; y < y_offset + y_size2; y++) {
489       for (x = 0; x < x_offset; x++) {
490         match_proto->data ()[x][y] = proto[x][y - y_offset] *
491           test_proto->n_samples ();
492         sum += match_proto->data ()[x][y];
493       }
494 
495       for (x = x_offset + xsize2; x < xsize; x++) {
496         match_proto->data ()[x][y] = proto[x][y - y_offset] *
497           test_proto->n_samples ();
498         sum += match_proto->data ()[x][y];
499       }
500 
501       for (x = x_offset; x < x_offset + xsize2; x++) {
502         match_proto->data ()[x][y] =
503           proto[x][y - y_offset] * test_proto->data ()[x - x_offset][y];
504         sum += match_proto->data ()[x][y];
505       }
506     }
507   }
508   else {
509     y_size = ysize;
510     y_size2 = test_proto->y_size ();
511     y_offset = (y_size - y_size2) / 2;
512 
513     match_proto = new CHAR_PROTO (xsize,
514       y_size,
515       nsamples * test_proto->n_samples (),
516       0, '\0');
517 
518     for (y = 0; y < y_offset; y++)
519     for (x = 0; x < xsize; x++) {
520       match_proto->data ()[x][y] =
521         proto[x][y] * test_proto->n_samples ();
522       sum += match_proto->data ()[x][y];
523     }
524 
525     for (y = y_offset + y_size2; y < y_size; y++)
526     for (x = 0; x < xsize; x++) {
527       match_proto->data ()[x][y] =
528         proto[x][y] * test_proto->n_samples ();
529       sum += match_proto->data ()[x][y];
530     }
531 
532     for (y = y_offset; y < y_offset + y_size2; y++) {
533       for (x = 0; x < x_offset; x++) {
534         match_proto->data ()[x][y] =
535           proto[x][y] * test_proto->n_samples ();
536         sum += match_proto->data ()[x][y];
537       }
538 
539       for (x = x_offset + xsize2; x < xsize; x++) {
540         match_proto->data ()[x][y] =
541           proto[x][y] * test_proto->n_samples ();
542         sum += match_proto->data ()[x][y];
543       }
544 
545       for (x = x_offset; x < x_offset + xsize2; x++) {
546         match_proto->data ()[x][y] = proto[x][y] *
547           test_proto->data ()[x - x_offset][y - y_offset];
548         sum += match_proto->data ()[x][y];
549       }
550     }
551   }
552 
553   score = (1.0 - sum /
554     (xsize * y_size * nsamples * test_proto->n_samples ()));
555 
556   if (tessedit_mm_debug) {
557     if (score < 0) {
558       tprintf ("Match score %f\n", score);
559       tprintf ("x: %d, y: %d, ns: %d, nt: %d, dx %d, dy: %d\n",
560         xsize, y_size, nsamples, test_proto->n_samples (),
561         x_offset, y_offset);
562       for (y = 0; y < y_size; y++) {
563         tprintf ("\n%d", y);
564         for (x = 0; x < xsize; x++)
565           tprintf ("\t%d", match_proto->data ()[x][y]);
566 
567       }
568       tprintf ("\n");
569       fflush(debug_fp);
570     }
571   }
572 
573 #ifndef GRAPHICS_DISABLED
574   if (tessedit_display_mm) {
575     tprintf ("Match score %f\n", score);
576     display_images (this->make_image (),
577       test_proto->make_image (), match_proto->make_image ());
578   }
579   else if (demo_word != 0) {
580     if (demo_word > 0)
581       display_image (test_proto->make_image (), "Test sample",
582         300, 400, FALSE);
583     else
584       display_image (this->make_image (), "Test sample", 300, 400, FALSE);
585 
586     display_image (match_proto->make_image (), "Best match",
587       700, 400, TRUE);
588   }
589 #endif
590 
591   delete match_proto;
592 
593   return score;
594 }
595 
596 
enlarge_prototype(inT32 new_xsize,inT32 new_ysize)597 void CHAR_PROTO::enlarge_prototype(inT32 new_xsize, inT32 new_ysize) {
598   float *old_proto_data = proto_data;
599   float **old_proto = proto;
600   inT32 old_xsize = xsize;
601   inT32 old_ysize = ysize;
602   inT32 x_offset;
603   inT32 y_offset;
604   inT32 x;
605   inT32 y;
606 
607   ASSERT_HOST (new_xsize >= xsize && new_ysize >= ysize);
608 
609   xsize = new_xsize;
610   ysize = new_ysize;
611   ALLOC_2D_ARRAY(xsize, ysize, proto_data, proto, float);
612   x_offset = (xsize - old_xsize) / 2;
613   y_offset = (ysize - old_ysize) / 2;
614 
615   for (y = 0; y < y_offset; y++)
616     for (x = 0; x < xsize; x++)
617       proto[x][y] = nsamples;
618 
619   for (y = y_offset + old_ysize; y < ysize; y++)
620     for (x = 0; x < xsize; x++)
621       proto[x][y] = nsamples;
622 
623   for (y = y_offset; y < y_offset + old_ysize; y++) {
624     for (x = 0; x < x_offset; x++)
625       proto[x][y] = nsamples;
626 
627     for (x = x_offset + old_xsize; x < xsize; x++)
628       proto[x][y] = nsamples;
629 
630     for (x = x_offset; x < x_offset + old_xsize; x++)
631       proto[x][y] = old_proto[x - x_offset][y - y_offset];
632   }
633 
634   FREE_2D_ARRAY(old_proto_data, old_proto);
635 }
636 
637 
add_sample(CHAR_SAMPLE * sample)638 void CHAR_PROTO::add_sample(CHAR_SAMPLE *sample) {
639   inT32 x_offset;
640   inT32 y_offset;
641   inT32 x;
642   inT32 y;
643   IMAGELINE imline_s;
644   inT32 sample_xsize = sample->image ()->get_xsize ();
645   inT32 sample_ysize = sample->image ()->get_ysize ();
646 
647   x_offset = (xsize - sample_xsize) / 2;
648   y_offset = (ysize - sample_ysize) / 2;
649 
650   ASSERT_HOST (x_offset >= 0 && y_offset >= 0);
651 
652   for (y = 0; y < y_offset; y++)
653     for (x = 0; x < xsize; x++)
654       proto[x][y]++;             // Treat pixels outside the
655   // range as white
656   for (y = y_offset + sample_ysize; y < ysize; y++)
657     for (x = 0; x < xsize; x++)
658       proto[x][y]++;
659 
660   for (y = y_offset; y < y_offset + sample_ysize; y++) {
661     sample->image ()->fast_get_line (0,
662       y - y_offset, sample_xsize, &imline_s);
663     for (x = x_offset; x < x_offset + sample_xsize; x++) {
664       if (imline_s.pixels[x - x_offset] == BINIM_WHITE)
665         proto[x][y]++;
666       else
667         proto[x][y]--;
668     }
669 
670     for (x = 0; x < x_offset; x++)
671       proto[x][y]++;
672 
673     for (x = x_offset + sample_xsize; x < xsize; x++)
674       proto[x][y]++;
675   }
676 
677   nsamples++;
678 }
679 
680 
make_image()681 IMAGE *CHAR_PROTO::make_image() {
682   IMAGE *image;
683   IMAGELINE imline_p;
684   inT32 x;
685   inT32 y;
686 
687   ASSERT_HOST (nsamples != 0);
688 
689   image = new (IMAGE);
690   image->create (xsize, ysize, 8);
691 
692   for (y = 0; y < ysize; y++) {
693     image->fast_get_line (0, y, xsize, &imline_p);
694 
695     for (x = 0; x < xsize; x++) {
696       imline_p.pixels[x] = 128 +
697         (uinT8) ((proto[x][y] * 128.0) / (0.00001 + nsamples));
698     }
699 
700     image->fast_put_line (0, y, xsize, &imline_p);
701   }
702   return image;
703 }
704