]> Creatis software - creaImageIO.git/blob - src/creaImageIOMultiThreadImageReader.cpp
#3264 creaImageIO Feature New Normal - Add Dicom Tags to the DB sqlite
[creaImageIO.git] / src / creaImageIOMultiThreadImageReader.cpp
1 /*
2 # ---------------------------------------------------------------------
3 #
4 # Copyright (c) CREATIS (Centre de Recherche en Acquisition et Traitement de l'Image 
5 #                        pour la Santé)
6 # Authors : Eduardo Davila, Frederic Cervenansky, Claire Mouton
7 # Previous Authors : Laurent Guigues, Jean-Pierre Roux
8 # CreaTools website : www.creatis.insa-lyon.fr/site/fr/creatools_accueil
9 #
10 #  This software is governed by the CeCILL-B license under French law and 
11 #  abiding by the rules of distribution of free software. You can  use, 
12 #  modify and/ or redistribute the software under the terms of the CeCILL-B 
13 #  license as circulated by CEA, CNRS and INRIA at the following URL 
14 #  http://www.cecill.info/licences/Licence_CeCILL-B_V1-en.html 
15 #  or in the file LICENSE.txt.
16 #
17 #  As a counterpart to the access to the source code and  rights to copy,
18 #  modify and redistribute granted by the license, users are provided only
19 #  with a limited warranty  and the software's author,  the holder of the
20 #  economic rights,  and the successive licensors  have only  limited
21 #  liability. 
22 #
23 #  The fact that you are presently reading this means that you have had
24 #  knowledge of the CeCILL-B license and that you accept its terms.
25 # ------------------------------------------------------------------------
26 */
27
28
29 #include <creaImageIOMultiThreadImageReader.h>
30 #include <creaImageIOImageReader.h>
31 #include <wx/utils.h>
32 #include <creaImageIOSystem.h>
33
34         #include <creaImageIODicomImageReader.h>
35
36
37 #include <creaImageIOGimmick.h>
38 #ifdef _DEBUG
39 #define new DEBUG_NEW
40 #endif
41 namespace creaImageIO
42 {
43
44   //=====================================================================
45   void MultiThreadImageReaderUser::MultiThreadImageReaderSendEvent
46   ( const std::string& filename,
47     EventType type,
48     vtkImageData* image)
49   {
50     wxMutexLocker lock(mMultiThreadImageReaderUserMutex);
51     this->OnMultiThreadImageReaderEvent(filename,type,image);
52   }
53   //=====================================================================
54
55   class wxThreadEED2
56   { 
57   public:
58           void Delete() {  printf("EED wxThreadEED2::Delete() \n"); }
59           int Run() { printf("EED wxThreadEED2::Run() \n"); return 0;}
60           void Pause() { printf("EED wxThreadEED2::Pause() \n"); }
61           void Create() { printf("EED wxThreadEED2::Create() \n"); }
62           bool IsAlive() { printf("EED wxThreadEED2::IsAlive() \n"); return false; }
63           bool TestDestroy() { printf("EED wxThreadEED2::TestDestroy() \n"); return false; }
64           int GetCurrentId() { printf("EED wxThreadEED2::GetCurrentId() \n"); return -999; }      
65   };
66   
67   //=====================================================================
68 //EED 2018-08-20
69 //  class ThreadedImageReader: public wxThread
70   class ThreadedImageReader: public wxThreadEED2
71   {
72   public:
73     ThreadedImageReader(MultiThreadImageReader* tir) :
74       mMultiThreadImageReader(tir)
75     {}
76     void* Entry();
77     void  OnExit();
78     vtkImageData* Read(const std::string& filename);
79         struct deleter
80         {
81                 void operator()(ThreadedImageReader* p)
82                 {
83                         p->Delete();
84                 }
85         };
86         friend struct deleter;
87
88   private:
89     ImageReader mReader;
90     MultiThreadImageReader* mMultiThreadImageReader;
91   };
92
93   //=====================================================================
94
95   
96   //=====================================================================
97   MultiThreadImageReader::MultiThreadImageReader(int number_of_threads)
98     : //mDoNotSignal(false),
99       mReader(NULL),
100       mTotalMem(0),
101       mTotalMemMax(1000000)
102   {
103     //    std::cout << "#### MultiThreadImageReader::MultiThreadImageReader("
104     //        << " #threads= " << number_of_threads <<" )"<<std::endl;
105
106           mDone = false;
107     // Create the threads
108         
109     for (int i=0; i<number_of_threads; i++) 
110     {
111                 //ThreadedImageReader* t = new ThreadedImageReader(this);
112                 boost::shared_ptr<ThreadedImageReader> t(new ThreadedImageReader(this), ThreadedImageReader::deleter());
113                 mThreadedImageReaderList.push_back(t);
114                 std::cout << "  ===> Thread "<<i <<" successfully added"<< std::endl;
115       } // for
116     mNumberOfThreadedReadersRunning = 0;
117     // Init the queue
118     mQueue.set(mComparator);
119     mQueue.set(mIndexer);
120     // 
121     // no thread : alloc self reader
122 //    if (number_of_threads==0)
123 //      {
124         mReader = new ImageReader();
125 //      }
126   }
127   //=====================================================================
128
129
130   //=====================================================================
131   bool MultiThreadImageReader::Start()
132   {
133     //    std::cout << "#### MultiThreadImageReader::Start()"
134     //                <<std::endl;
135           if (mNumberOfThreadedReadersRunning > 0) return true;
136           
137     ThreadedImageReaderListType::iterator i;
138     for (i =mThreadedImageReaderList.begin();
139          i!=mThreadedImageReaderList.end();
140          i++)
141     {
142                 (*i)->Create();
143                 if ( (*i)->Run() != wxTHREAD_NO_ERROR )
144                   {
145                         std::cout << "ERROR starting a thread"<< std::endl;
146                         return false;
147                   }     else  {
148                                         std::cout << "  ===> Thread "<<(*i)->GetCurrentId()
149                                                   <<" successfully created"<< std::endl;
150                   } // if
151                 } // for
152                 wxMutexLocker locker(GetMultiThreadImageReaderUserMutex());
153                 //    std::cout << "EO Start : #Threads running = "
154                 //                    << mNumberOfThreadedReadersRunning<<std::endl;
155     return true;
156   }
157   //=====================================================================
158
159   //=====================================================================
160   void MultiThreadImageReader::Stop()
161   { 
162 //                  std::cout << "#### MultiThreadImageReader::Stop()"
163 //            <<std::endl;
164   //  std::cout << "Sending stop order to the threads..."<<std::endl;
165   
166           if (mDone) return;
167
168     ThreadedImageReaderListType::iterator i;
169     for (i =mThreadedImageReaderList.begin();
170          i!=mThreadedImageReaderList.end();
171          i++)
172     { 
173                 std::cout << "  ===> Thread "<<(*i)->GetCurrentId()
174                               <<" successfully stopped"<< std::endl;
175                   if((*i)->IsAlive())
176                   {
177                           (*i)->Pause();
178                           (*i).reset();
179 //                        (*i)->Delete();
180                   } // if i
181     } // for
182    mThreadedImageReaderList.clear();
183     // Wait a little to be sure that all threads have stopped
184     // A better way to do this ?
185     //    wxMilliSleep(1000);
186     // New method : the threads generate a stop event when they have finished
187     // We wait until all threads have stopped
188 //        std::cout << "Waiting for stop signals..."<<std::endl;
189     do 
190       {
191         // Sleep a little
192                 wxMilliSleep(10);
193         // Lock
194         {
195           wxMutexLocker locker(GetMultiThreadImageReaderUserMutex());
196 //                std::cout << "#Threads running = "
197 //                          << mNumberOfThreadedReadersRunning<<std::endl;
198           // Break if all readers have stopped
199           if (mNumberOfThreadedReadersRunning <= 0) 
200             {
201               break;
202             }
203         }
204       } 
205     while (true);
206 //        std::cout << "All threads stopped : OK "<<std::endl;
207
208
209         CleanMImagesMap();
210         
211         mDone = true;
212
213   }
214   //=====================================================================
215
216   
217    void MultiThreadImageReader::CleanMImagesMap()
218   {
219      ImageMapType::iterator j;
220      for (j=mImages.begin(); j!=mImages.end(); ++j)
221          {
222                 delete j->first;
223      } //for 
224      mImages.clear();
225   }
226   
227   //=====================================================================
228   MultiThreadImageReader::~MultiThreadImageReader()
229   {
230     //    std::cout << "#### MultiThreadImageReader::~MultiThreadImageReader()"
231     //        <<std::endl;
232     Stop();
233     if (mReader) delete mReader;
234         mThreadedImageReaderList.clear();
235
236         CleanMImagesMap();
237         
238 /*      
239         ImageMapType::iterator it;
240         for (it=mImages.begin() ; it!=mImages.end(); it++)
241         {
242                         printf("MultiThreadImageReader::~MultiThreadImageReader  %s ", it.first->GetFilename() );
243         } // for it
244 */
245         
246   }
247   //=====================================================================
248
249   //=====================================================================
250   void MultiThreadImageReader::UpdateUnloadPriority(ImageToLoadPtr p, 
251                                                     int priority)
252   {
253     // not in unload queue : ciao
254     if (p->UnloadIndex()<0) return;
255     int old_prio = p->GetPriority();
256     if (priority > old_prio) 
257       {
258         p->SetPriority(priority);
259         mUnloadQueue.downsort(p->UnloadIndex());
260       }
261     else if ( old_prio > priority )
262       {
263         p->SetPriority(priority);
264         mUnloadQueue.upsort(p->UnloadIndex());
265      }
266   }
267   //=====================================================================
268   // function to read attributes for a file
269   void MultiThreadImageReader::getAttributes(const std::string filename, 
270           std::map <std::string , std::string> &infos,std::vector<std::string> i_attr)
271   {
272           mReader->getAttributes(filename, infos, i_attr);
273   }
274
275   //=====================================================================
276   void MultiThreadImageReader::Request( MultiThreadImageReaderUser* user,
277                                         const std::string& filename, 
278                                         int priority )
279   {
280         wxMutexLocker lock(GetMultiThreadImageReaderUserMutex()); //mMutex);
281
282           if (mNumberOfThreadedReadersRunning==0)
283 //    if (mThreadedImageReaderList.size()==0) 
284       {
285         // no detached reader : use self reader
286         ImageToLoad itl(user,filename);
287         ImageMapType::iterator i = mImages.find(&itl);
288         if (i!=mImages.end())
289           {
290             ImageToLoadPtr pitl = const_cast<ImageToLoadPtr>(i->first);
291             // Already inserted
292             if (pitl->GetImage() != 0)
293               {
294                 // Already read
295                 pitl->SetUser(user);
296                 UpdateUnloadPriority(pitl,priority);
297                 SignalImageRead(pitl,false);
298                 return; // pitl->GetImage();
299               }
300           }
301         ImageToLoadPtr pitl = new ImageToLoad(user,filename,0);
302         mImages[pitl] = 0;
303         pitl->SetImage(mReader->ReadImage(filename));
304         UpdateUnloadPriority(pitl,priority);
305         SignalImageRead(pitl,true);
306         //      return pitl->GetImage();
307         return;
308       }
309
310     ImageToLoad itl(user,filename);
311     ImageMapType::iterator i = mImages.find(&itl);
312     if (i!=mImages.end())
313       {
314         // Already inserted
315         if (i->first->GetImage() != 0)
316           {
317             // Already read : ok :signal the user
318             UpdateUnloadPriority(i->first,priority);
319             SignalImageRead(i->first,false);
320             return;
321           }
322         /// Already requested : change the priority
323         ImageToLoadPtr pitl = const_cast<ImageToLoadPtr>(i->first);
324         pitl->SetPriority(priority);
325         // Already in queue
326         if (pitl->Index()>=0) 
327           {
328             // Re-sort the queue
329             mQueue.upsort(pitl->Index());
330           }
331         // Not read but not in queue = being read = ok
332         else 
333           {
334             
335           }
336       }
337     else 
338       {
339         // Never requested before or unloaded 
340         ImageToLoadPtr pitl = new ImageToLoad(user,filename,priority);
341         mImages[pitl] = 0;
342         mQueue.insert(pitl);
343       }
344   }
345   //=====================================================================
346
347   //=====================================================================
348   void MultiThreadImageReader::OnMultiThreadImageReaderEvent
349   (const std::string& filename,
350    MultiThreadImageReaderUser::EventType e,
351    vtkImageData* image)
352   {
353     if ((e==MultiThreadImageReaderUser::ImageLoaded) &&
354         (filename == mRequestedFilename))
355       {
356         mRequestedImage = image;
357       }
358     else if (e==MultiThreadImageReaderUser::ThreadedReaderStarted)
359       {
360         mNumberOfThreadedReadersRunning++;
361         //      std::cout << "#TR=" << mNumberOfThreadedReadersRunning << std::endl;
362       }
363     else if (e==MultiThreadImageReaderUser::ThreadedReaderStopped)
364       {
365         
366                  mNumberOfThreadedReadersRunning--;
367         //      std::cout << "#TR=" << mNumberOfThreadedReadersRunning << std::endl;
368       }
369   }
370   //=====================================================================
371
372   //=====================================================================
373   vtkImageData* MultiThreadImageReader::GetImage(const std::string& filename)
374   {
375
376         printf("EED MultiThreadImageReader::GetImage  Start\n");        
377
378     
379     do 
380       {
381         //      wxMutexLocker lock(GetMultiThreadImageReaderUserMutex()); //mMutex);
382                 
383         //     std::cout << "** MultiThreadImageReader::GetImage('"<<filename
384         //             <<"') lock ok"
385         //               <<std::endl;
386     
387         //                if (mNumberOfThreadedReadersRunning==0)
388         //      if (mThreadedImageReaderList.size()==0)
389         if (true)
390         {
391         printf("EED MultiThreadImageReader::GetImage  1\n");    
392             ImageToLoad itl(this,filename);
393         printf("EED MultiThreadImageReader::GetImage  2\n");    
394             ImageMapType::iterator i = mImages.find(&itl);
395             if (i!=mImages.end())
396             {
397         printf("EED MultiThreadImageReader::GetImage  2.1\n");  
398                         ImageToLoadPtr pitl = const_cast<ImageToLoadPtr>(i->first);
399         printf("EED MultiThreadImageReader::GetImage  2.2\n");  
400                         // Already inserted
401                         if (pitl->GetImage() != NULL)
402                         {
403                                 // Already read
404                                 UpdateUnloadPriority( pitl, GetMaximalPriorityWithoutLocking()+1 );
405                                 return pitl->GetImage();
406                         } // if pitl->GetImage()
407             } // if i
408                 
409         printf("EED MultiThreadImageReader::GetImage  3.1\n");  
410             ImageToLoadPtr pitl = new ImageToLoad(this,filename,0);
411         printf("EED MultiThreadImageReader::GetImage  3.2\n");  
412             mImages[pitl]               = NULL;
413             pitl->SetImage( mReader->ReadImage(filename) );
414         printf("EED MultiThreadImageReader::GetImage  3.3\n");  
415             UpdateUnloadPriority( pitl, GetMaximalPriorityWithoutLocking()+1 );
416         printf("EED MultiThreadImageReader::GetImage  3.4\n");  
417             return pitl->GetImage();
418         } // if true
419         
420         /*      
421         mRequestedFilename = filename;
422         mRequestedImage = 0;
423         ImageToLoad itl(this,filename);
424         ImageMapType::iterator i = mImages.find(&itl);
425         if (i!=mImages.end())
426           {
427             // Already inserted in queue
428             if (i->first->GetImage() != 0)
429               {
430                 // Already read : ok : return it 
431                 return i->first->GetImage();
432               }
433             /// Already requested : change the priority
434               ImageToLoadPtr pitl = const_cast<ImageToLoadPtr>(i->first);
435               pitl->SetPriority( GetMaximalPriorityWithoutLocking() + 1 );
436               pitl->SetUser( this );
437               // Already in queue
438               if (pitl->Index()>=0) 
439                 {
440                   // Re-sort the queue
441                   mQueue.upsort(pitl->Index());
442                 }
443               // Not read but not in queue = being read = ok
444               else 
445                 {
446                   pitl->SetUser( this );
447                 }
448           }
449         else 
450           {
451             
452             // Never requested before or unloaded 
453             ImageToLoadPtr pitl = 
454               new ImageToLoad(this,filename,
455                               GetMaximalPriorityWithoutLocking() + 1);
456             mImages[pitl] = 0;
457             mQueue.insert(pitl);
458           }
459         */
460       }
461     while (0);
462
463     //    std::cout << "Waiting..."<<std::endl;
464
465     /*
466     // Waiting that it is read
467     int n = 0;
468     do 
469       {
470         //      std::cout << n++ << std::endl;
471         wxMilliSleep(10);
472         do 
473           {
474             //      wxMutexLocker lock(mMutex);
475             wxMutexLocker lock(GetMultiThreadImageReaderUserMutex());
476             if (mRequestedImage!=0) 
477               {
478                 return mRequestedImage;
479               } 
480           }
481         while (0);
482       }
483     while (true);
484     // 
485     */
486         printf("EED MultiThreadImageReader::GetImage  END\n");  
487         
488   }
489   //=====================================================================
490   
491   //=====================================================================
492   void MultiThreadImageReader::SignalImageRead(ImageToLoadPtr p, 
493                                                bool purge)
494   {
495     
496 //    std::cout << "MultiThreadImageReader::SignalImageRead" <<std::endl;
497     //    std::cout << "this="<<this <<std::endl;
498     //    std::cout << "user="<<p->GetUser() <<std::endl;
499
500     if ( p->GetUser() == this ) 
501         {
502       GetMultiThreadImageReaderUserMutex().Unlock();
503         }
504
505     p->GetUser()->MultiThreadImageReaderSendEvent
506       (p->GetFilename(),
507        MultiThreadImageReaderUser::ImageLoaded,
508        p->GetImage());
509
510     /*
511       AN ATTEMPT TO UNLOAD OLDEST IMAGE IF EXCEEDED A CERTAIN MEMORY QUOTA
512       BUGGY : TO FIX 
513     */
514     if (!purge)  return;
515     GimmickMessage(5,"Image '"<<p->GetFilename()<<"' read"<<std::endl);
516
517     //    wxMutexLocker lock(GetMultiThreadImageReaderUserMutex());
518            
519     mUnloadQueue.insert(p);
520
521
522 //EED 2017-01-01 Migration VTK7
523 #if VTK_MAJOR_VERSION <= 5
524     p->GetImage()->UpdateInformation();
525     p->GetImage()->PropagateUpdateExtent();
526     long ImMem = p->GetImage()->GetEstimatedMemorySize();
527 #else
528         int ext[6];
529         int dim[3];
530         p->GetImage()->GetExtent(ext);
531         dim[0]          = ext[1]-ext[0]+1;
532         dim[1]          = ext[3]-ext[2]+1;
533         dim[2]          = ext[5]-ext[4]+1;
534     long ImMem  = dim[0]*dim[1]*dim[2]*p->GetImage()->GetScalarSize();;
535 #endif
536     mTotalMem += ImMem;
537
538     GimmickMessage(5,"==> Image in memory = "<<mUnloadQueue.size()<<std::endl);
539     GimmickMessage(5,"==> Total mem       = "<<mTotalMem<<" Ko"<<std::endl);
540
541     //  return;
542
543     while (mTotalMem > mTotalMemMax)
544       {
545         GimmickMessage(5,
546                        "   ! Exceeded max of "
547                        << mTotalMemMax << " Ko : unloading oldest image ... "
548                        << std::endl);
549         if ( mUnloadQueue.size() <= 1 ) 
550           {
551              GimmickMessage(5,
552                             "   Only one image : cannot load AND unload it !!"
553                             <<std::endl);
554             break; 
555             
556           }
557         ImageToLoadPtr unload = mUnloadQueue.remove_top();
558         MultiThreadImageReaderUser* user = unload->GetUser();
559
560         /*
561         if ((user!=0)&&(user!=this)) 
562           {
563             user->GetMultiThreadImageReaderUserMutex().Lock();
564           }
565         */
566
567         std::string filename = unload->GetFilename();
568
569         GimmickMessage(5,"'" << filename << "'" << std::endl);
570
571 //EED 2017-01-01 Migration VTK7
572 #if VTK_MAJOR_VERSION <= 5
573         mTotalMem -= unload->GetImage()->GetEstimatedMemorySize();
574 #else
575         int ext[6];
576         int dim[3];
577         unload->GetImage()->GetExtent(ext);
578         dim[0]          = ext[1]-ext[0]+1;
579         dim[1]          = ext[3]-ext[2]+1;
580         dim[2]          = ext[5]-ext[4]+1;
581         mTotalMem -= dim[0]*dim[1]*dim[2]*unload->GetImage()->GetScalarSize();
582 #endif
583
584         GimmickMessage(5," ==> Total mem = "<<mTotalMem<<" Ko "<<std::endl);
585
586         if (user!=0) 
587           {
588             //      std::cout << "unlock..."<<std::endl;
589             //   user->GetMultiThreadImageReaderUserMutex().Unlock();
590             //      std::cout << "event"<<std::endl;
591             user->MultiThreadImageReaderSendEvent
592               (filename,
593                MultiThreadImageReaderUser::ImageUnloaded,
594                0);
595             //      std::cout << "event ok"<<std::endl;
596           }     
597
598         if (unload->Index()>=0)
599           {
600             // GimmickMessage(5,"still in queue"<<std::endl);
601           }
602         unload->Index() = -1;
603
604
605         ImageMapType::iterator it = mImages.find(unload);
606         if (it!=mImages.end())
607           {
608             mImages.erase(it);
609           }
610         //          std::cout << "delete..."<<std::endl;
611         delete unload;
612         //          std::cout << "delete ok."<<std::endl;
613
614       }
615   }
616   //=====================================================================
617
618   //=====================================================================
619   int MultiThreadImageReader::GetMaximalPriority()
620   { 
621     wxMutexLocker lock(GetMultiThreadImageReaderUserMutex()); //mMutex);
622     return GetMaximalPriorityWithoutLocking();
623   }
624   //=====================================================================
625
626
627   //=====================================================================
628   int MultiThreadImageReader::GetMaximalPriorityWithoutLocking()
629   { 
630     long max = 0;
631     if (mQueue.size()>0) 
632       {
633         max = mQueue.top()->GetPriority();
634       }
635     if (mUnloadQueue.size()>0)
636       {
637         int max2 = mUnloadQueue.top()->GetPriority();
638         if (max2>max) max=max2;
639       }
640     return max;
641   }
642   //=====================================================================
643
644
645   //=====================================================================
646   //=====================================================================
647   //=====================================================================
648   //=====================================================================
649
650   //=====================================================================
651   void*  ThreadedImageReader::Entry()
652   {
653     //    std::cout << "### Thread "<<GetCurrentId()<<"::Entry()"
654     //                << std::endl;
655
656     mMultiThreadImageReader->MultiThreadImageReaderSendEvent
657       ("",
658        MultiThreadImageReaderUser::ThreadedReaderStarted,
659        0);
660
661     // While was not deleted 
662     while (!TestDestroy())
663       {
664                 //std::cout << "### Thread "<<GetCurrentId()<<" still alive"  << std::endl;
665           
666         // Lock the mutex
667         mMultiThreadImageReader->MultiThreadImageReaderEventLock();
668         //mMutex.Lock();
669         // If image in queue
670         if (mMultiThreadImageReader->mQueue.size()>0)
671           {
672             MultiThreadImageReader::ImageToLoadPtr i = 
673               mMultiThreadImageReader->mQueue.remove_top();
674
675             mMultiThreadImageReader->MultiThreadImageReaderEventUnlock();
676             //mMutex.Unlock();
677
678             
679             //      std::cout << "### Thread "<<GetCurrentId()<<" : reading '"
680             //                << i->GetFilename() << "'" << std::endl;
681             
682             // Do the job
683             vtkImageData* im = Read(i->GetFilename());
684
685             // Store it in the map
686             mMultiThreadImageReader->MultiThreadImageReaderEventLock();
687             //mMutex.Lock();
688             MultiThreadImageReader::ImageToLoad itl(0,i->GetFilename());
689             MultiThreadImageReader::ImageMapType::iterator it = 
690               mMultiThreadImageReader->mImages.find(&itl);
691             MultiThreadImageReader::ImageToLoadPtr 
692               pitl = const_cast<MultiThreadImageReader::ImageToLoadPtr>
693               (it->first);
694             pitl->SetImage(im);
695             mMultiThreadImageReader->SignalImageRead(pitl,true);//i->GetFilename());
696             mMultiThreadImageReader->MultiThreadImageReaderEventUnlock();           //mMutex.Unlock();
697             
698             //      std::cout << "### Thread "<<GetCurrentId()<<" : reading '"
699             //                << i->GetFilename() << "' : DONE" << std::endl;
700             
701           }     else  {
702             mMultiThreadImageReader->MultiThreadImageReaderEventUnlock();
703             //mMutex.Unlock();
704             // Wait a little to avoid blocking 
705             wxMilliSleep(10);
706           }
707       };
708     //    std::cout << "### Thread "<<GetCurrentId()<<" stopping"
709     //                << std::endl;
710        
711     return 0;
712   }
713   //=====================================================================
714
715   //=====================================================================
716   void ThreadedImageReader::OnExit()
717   {
718     mMultiThreadImageReader->MultiThreadImageReaderSendEvent
719       ("",
720        MultiThreadImageReaderUser::ThreadedReaderStopped,
721        0);
722   }
723   //=====================================================================
724
725   //=====================================================================
726   vtkImageData* ThreadedImageReader::Read(const std::string& filename)
727   {
728     return mReader.ReadImage(filename);
729   }
730   //=====================================================================
731
732 } // namespace creaImageIO