source: trunk/model/shooting.py @ 300

Revision 300, 16.6 KB checked in by fma, 5 years ago (diff)

Added number of pictures on progressbar

  • Property svn:keywords set to Id
Line 
1# -*- coding: iso-8859-1 -*-
2
3""" Panohead remote control.
4
5License
6=======
7
8 - B{papywizard} (U{http://trac.gbiloba.org/papywizard}) is Copyright:
9  - (C) 2007-2008 Frédéric Mantegazza
10
11This software is governed by the B{CeCILL} license under French law and
12abiding by the rules of distribution of free software.  You can  use,
13modify and/or redistribute the software under the terms of the CeCILL
14license as circulated by CEA, CNRS and INRIA at the following URL
15U{http://www.cecill.info}.
16
17As a counterpart to the access to the source code and  rights to copy,
18modify and redistribute granted by the license, users are provided only
19with a limited warranty  and the software's author,  the holder of the
20economic rights,  and the successive licensors  have only  limited
21liability.
22
23In this respect, the user's attention is drawn to the risks associated
24with loading,  using,  modifying and/or developing or reproducing the
25software by the user in light of its specific status of free software,
26that may mean  that it is complicated to manipulate,  and  that  also
27therefore means  that it is reserved for developers  and  experienced
28professionals having in-depth computer knowledge. Users are therefore
29encouraged to load and test the software's suitability as regards their
30requirements in conditions enabling the security of their systems and/or
31data to be ensured and,  more generally, to use and operate it in the
32same conditions as regards security.
33
34The fact that you are presently reading this means that you have had
35knowledge of the CeCILL license and that you accept its terms.
36
37Module purpose
38==============
39
40Model
41
42Implements
43==========
44
45- Shooting
46
47@author: Frédéric Mantegazza
48@copyright: (C) 2007-2008 Frédéric Mantegazza
49@license: CeCILL
50"""
51
52__revision__ = "$Id$"
53
54import time
55
56from papywizard.common.loggingServices import Logger
57from papywizard.common.signal import Signal
58from papywizard.common.configManager import ConfigManager
59from papywizard.common.exception import HardwareError
60from papywizard.common.data import Data
61from papywizard.model.camera import Camera
62from papywizard.model.mosaic import Mosaic
63
64
65class Shooting(object):
66    """ Shooting model.
67    """
68    def __init__(self, realHardware, simulatedHardware):
69        """ Init the object.
70
71        @param realHardware: real hardware head
72        @type realHardware: {Head}
73
74        @param simulatedHardware: simulated hardware head
75        @type simulatedHardware: {HeadSimulation}
76        """
77        self.__running = False
78        self.__suspend = False
79        self.__stop = False
80        self.__yawCoef = "--"
81        self.__pitchCoef = "--"
82        self.__progress = self.__progress = {'fraction': 0., 'text': "-/-"}
83        self.__sequence = "Idle"
84        self.__setParams = None
85        self.__manualShoot = False
86
87        self.realHardware = realHardware
88        self.simulatedHardware = simulatedHardware
89        self.hardware = self.simulatedHardware
90        self.switchToRealHardwareSignal = Signal()
91        self.camera = Camera()
92
93        self.yawStart = 0.
94        self.pitchStart = 0.
95        self.yawEnd = 0.
96        self.pitchEnd = 0.
97        self.position = self.hardware.readPosition()
98
99        #self.__computeParams('startEnd')
100
101    def __getStabilizationDelay(self):
102        """
103        """
104        return ConfigManager().getFloat('Preferences', 'SHOOTING_STABILIZATION_DELAY')
105   
106    def __setStabilizationDelay(self, stabilizationDelay):
107        """
108        """
109        ConfigManager().setFloat('Preferences', 'SHOOTING_STABILIZATION_DELAY', stabilizationDelay, 1)
110   
111    stabilizationDelay = property(__getStabilizationDelay, __setStabilizationDelay)
112   
113    def __getOverlap(self):
114        """
115        """
116        return ConfigManager().getFloat('Preferences', 'SHOOTING_OVERLAP')
117   
118    def __setOverlap(self, overlap):
119        """
120        """
121        ConfigManager().setFloat('Preferences', 'SHOOTING_OVERLAP', overlap, 2)
122       
123    overlap = property(__getOverlap, __setOverlap)
124   
125    def __getCameraOrientation(self):
126        """
127        """
128        return ConfigManager().get('Preferences', 'SHOOTING_CAMERA_ORIENTATION')
129   
130    def __setCameraOrientation(self, cameraOrientation):
131        """
132        """
133        ConfigManager().set('Preferences', 'SHOOTING_CAMERA_ORIENTATION', cameraOrientation)
134
135    cameraOrientation = property(__getCameraOrientation, __setCameraOrientation)
136
137    def __getYawFov(self):
138        """
139        """
140        cameraFov = self.camera.getYawFov(self.cameraOrientation)
141        return abs(self.yawEnd - self.yawStart) + cameraFov
142
143    yawFov = property(__getYawFov, "Total yaw FoV")
144
145    def __getPitchFov(self):
146        """
147        """
148        cameraFov = self.camera.getPitchFov(self.cameraOrientation)
149        return abs(self.pitchEnd - self.pitchStart) + cameraFov
150
151    pitchFov = property(__getPitchFov, "Total pitch FoV")
152
153    def __getYawNbPicts(self):
154        """
155        """
156        cameraFov = self.camera.getYawFov(self.cameraOrientation)
157        if round(self.yawFov - cameraFov, 1) >= 0.1:
158            nbPicts = int(((self.yawFov - self.overlap * cameraFov) / (cameraFov * (1 - self.overlap))) + 1)
159        else:
160            nbPicts = 1
161        return nbPicts
162
163    yawNbPicts = property(__getYawNbPicts, "Yaw nb picts")
164
165    def __getPitchNbPicts(self):
166        """
167        """
168        cameraFov = self.camera.getPitchFov(self.cameraOrientation)
169        if round(self.pitchFov - cameraFov, 1) >= 0.1:
170           nbPicts = int(((self.pitchFov - self.overlap * cameraFov) / (cameraFov * (1 - self.overlap))) + 1)
171        else:
172            nbPicts = 1
173        return nbPicts
174
175    pitchNbPicts = property(__getPitchNbPicts, "Pitch nb picts")
176
177    def __getRealYawOverlap(self):
178        """ Recompute real yaw overlap.
179        """
180        cameraFov = self.camera.getYawFov(self.cameraOrientation)
181        if self.yawNbPicts > 1:
182            overlap = (self.yawNbPicts * cameraFov - self.yawFov) / (cameraFov * (self.yawNbPicts - 1))
183        else:
184            overlap = 1.
185        return overlap
186
187    yawRealOverlap = property(__getRealYawOverlap, "Real yaw overlap")
188
189    def __getRealPitchOverlap(self):
190        """ Recompute real pitch overlap.
191        """
192        cameraFov = self.camera.getPitchFov(self.cameraOrientation)
193        if self.pitchNbPicts > 1:
194            overlap = (self.pitchNbPicts * cameraFov - self.pitchFov) / (cameraFov * (self.pitchNbPicts - 1))
195        else:
196            overlap = 1.
197        return overlap
198
199    pitchRealOverlap = property(__getRealPitchOverlap, "Real pitch overlap")
200
201    #def __computeParams(self, setParam):
202        #""" Compute missing params from given params.
203
204        #@param setParam: given params type ('startEnd', 'fov', 'nbPict')
205        #@type setParam: str
206
207        #@todo: add fov and nbPicts
208        #"""
209        #if setParam == 'startEnd':
210            #self.yawFov = self.__getYawFov()
211            #self.pitchFov = self.__getPitchFov()
212            #self.yawNbPicts = self.__getYawNbPicts()
213            #self.pitchNbPicts = self.__getPitchNbPicts()
214            #self.yawRealOverlap = self.__getRealYawOverlap()
215            #self.pitchRealOverlap = self.__getRealPitchOverlap()
216        #elif setParam == 'fov':
217            #Logger().warning("Shooting.__computeParam(): 'fov' setting not yet implemented")
218        #elif setParam == 'nbPicts':
219            #Logger().warning("Shooting.__computeParam(): 'nbPicts' setting not yet implemented")
220        #else:
221            #raise ValueError("param must be in ('startEnd', 'fov', 'nbPict')")
222
223    def switchToRealHardware(self):
224        """ Use real hardware.
225        """
226        try:
227            #self.simulatedHardware.shutdown()
228            self.realHardware.init()
229            Logger().debug("Shooting.switchToRealHardware(): realHardware initialized")
230            self.position = self.realHardware.readPosition()
231            self.hardware = self.realHardware
232            self.switchToRealHardwareSignal.emit(True)
233        except HardwareError, message:
234            Logger().exception("Shooting.switchToRealHardware()") 
235            self.switchToRealHardwareSignal.emit(False, message)
236
237    def switchToSimulatedHardware(self):
238        """ Use simulated hardware.
239        """
240        self.realHardware.shutdown()
241        self.hardware = self.simulatedHardware
242        self.hardware.init()
243        self.position = self.hardware.readPosition()
244
245    def storeStartPosition(self):
246        """ Store current position as start position.
247        """
248        self.yawStart, self.pitchStart = self.hardware.readPosition()
249        Logger().debug("Shooting.storeStartPosition(): yaw=%.1f, pitch=%.1f" % (self.yawStart, self.pitchStart))
250        #self.__computeParams('startEnd')
251
252    def storeEndPosition(self):
253        """ Store current position as end position.
254        """
255        self.yawEnd, self.pitchEnd = self.hardware.readPosition()
256        Logger().debug("Shooting.storeEndPosition(): yaw=%.1f, pitch=%.1f" % (self.yawEnd, self.pitchEnd))
257        #self.__computeParams('startEnd')
258
259    def setYaw360(self):
260        """ Compute start/end yaw position for 360°.
261       
262        Récupérer position courante et calculer début et fin pour avoir
263        +-180°, overlap inclus
264        """
265        yaw, pitch = self.hardware.readPosition()
266        cameraFov = self.camera.getPitchFov(self.cameraOrientation)
267        self.yawStart = yaw - 180. + cameraFov * (1 - self.overlap) / 2.
268        self.yawEnd = yaw + 180. - cameraFov * (1 - self.overlap) / 2.
269        Logger().debug("Shooting.setYaw360(): startYaw=%.1f, endYaw=%.1f" % (self.yawStart, self.yawEnd))
270
271    def setPitch180(self):
272        """ Compute start/end pitch position for 180°.
273       
274        Récupérer position courante et calculer début et fin pour avoir
275        +-90°, overlap inclus
276        Tenir compte des butées softs !
277        """
278        yaw, pitch = self.hardware.readPosition()
279        cameraFov = self.camera.getPitchFov(self.cameraOrientation)
280        self.pitchStart = pitch - 90. + cameraFov * (1 - self.overlap) / 2.
281        self.pitchEnd = yaw + 90. - cameraFov * (1 - self.overlap) / 2.
282        Logger().debug("Shooting.setPitch180(): startPitch=%.1f, endPitch=%.1f" % (self.pitchStart, self.pitchEnd))
283
284    def setManualShoot(self, flag):
285        """ Turn on/off manual shoot.
286
287        In manual shoot mode, the head switch to suspend at each end of position.
288
289        @param flag: flag for manual shoot
290        @type flag: bool
291        """
292        self.__manualShoot = flag
293
294    def generate(self):
295        """ Generate all shooting positions.
296       
297        @return: shooting positions
298        @rtype: list of dict
299        """
300
301    def start(self):
302        """ Start pano shooting.
303        """
304        def checkSuspendStop():
305            """ Check if suspend or stop requested.
306            """
307            if self.__suspend:
308                Logger().info("Suspend")
309                self.__sequence = "Idle"
310                while self.__suspend:
311                    time.sleep(0.1)
312                Logger().info("Resume")
313            if self.__stop:
314                Logger().info("Stop")
315                raise StopIteration
316
317        Logger().trace("Shooting.start()")
318        self.__running = True
319
320        cameraFov = self.camera.getYawFov(self.cameraOrientation)
321        try:
322            yawInc = (self.yawFov - cameraFov) / (self.yawNbPicts - 1)
323        except ZeroDivisionError:
324            yawInc = self.yawFov - cameraFov
325        yawInc *= cmp(self.yawEnd, self.yawStart)
326        cameraFov = self.camera.getPitchFov(self.cameraOrientation)
327        try:
328            pitchInc = (self.pitchFov - cameraFov) / (self.pitchNbPicts - 1)
329        except ZeroDivisionError:
330            pitchInc = self.pitchFov - cameraFov
331        pitchInc *= cmp(self.pitchEnd, self.pitchStart)
332        mosaic = Mosaic(self.yawNbPicts, self.pitchNbPicts)
333
334        try:
335            data = Data()
336            data.addHeaderNode('focal', "%.1f" % self.camera.lens.focal)
337            data.addHeaderNode('fisheye', "%s" % self.camera.lens.fisheye)
338            data.addHeaderNode('sensorCoef', "%.1f" % self.camera.sensorCoef)
339            data.addHeaderNode('sensorRatio', "%s" % self.camera.sensorRatio)
340            data.addHeaderNode('cameraOrientation', "%s" % self.cameraOrientation)
341            data.addHeaderNode('nbPicts', "%d" % self.camera.nbPicts)
342            data.addHeaderNode('timeValue', "%.1f" % self.camera.timeValue)
343            data.addHeaderNode('stabilizationDelay', "%.1f" % self.stabilizationDelay)
344            data.addHeaderNode('overlap', "%.2f" % self.overlap)
345            data.addHeaderNode('yawRealOverlap', "%.2f" % self.yawRealOverlap)
346            data.addHeaderNode('pitchRealOverlap', "%.2f" % self.pitchRealOverlap)
347            data.addHeaderNode('template', type="mosaic", yaw="%d" % self.yawNbPicts, pitch="%d" % self.pitchNbPicts)
348
349            # Loop over all positions
350            totalNbPicts = self.yawNbPicts * self.pitchNbPicts
351            self.__progress = {'fraction': 0., 'text': "0/%d" % totalNbPicts}
352            for i, (yawCoef, pitchCoef) in enumerate(mosaic):
353                yaw = self.yawStart + yawCoef * yawInc
354                pitch = self.pitchStart + pitchCoef * pitchInc
355                Logger().debug("Shooting.start(): Goto yaw=%.1f pitch=%.1f" % (yaw, pitch))
356                self.__yawCoef = yawCoef
357                self.__pitchCoef = pitchCoef
358                Logger().info("Moving")
359                self.__sequence = "Moving"
360                self.hardware.gotoPosition(yaw, pitch)
361
362                Logger().info("Stabilization")
363                self.__sequence = "Stabilizing"
364                time.sleep(self.stabilizationDelay)
365
366                if self.__manualShoot:
367                    self.__suspend = True
368                    Logger().info("Manual shoot")
369
370                checkSuspendStop()
371
372                Logger().info("Shooting")
373                for pict in xrange(self.camera.nbPicts):
374                    Logger().debug("Shooting.start(): Shooting %d/%d" % (pict + 1, self.camera.nbPicts))
375                    self.__sequence = "Shooting %d/%d" % (pict + 1, self.camera.nbPicts)
376                    self.hardware.shoot(self.camera.timeValue)
377                    data.addImageNode(pict + 1, yaw, pitch)
378
379                    checkSuspendStop()
380
381                progressFraction = float((i + 1)) / float(totalNbPicts)
382                self.__progress = {'fraction': progressFraction, 'text': "%d/%d" % (i + 1, totalNbPicts)}
383
384            Logger().debug("Shooting.start(): finished")
385
386        except StopIteration:
387            Logger().debug("Shooting.start(): Stop detected")
388
389        self.__yawCoef = "--"
390        self.__pitchCoef = "--"
391        self.__sequence = "Idle"
392        self.__stop = False
393        self.__running = False
394
395    def getState(self):
396        """ Return shooting state.
397
398        @return: key 'yawPos': yaw position
399                     'pitchPos': pitch position
400                     'yawCoef': yaw mosaic coef
401                     'pitchCoef': pitch mosaic coef
402                     'progress': shooting progress (num of pict)
403                     'sequence': shooting sequence
404
405        @rtype: dict
406        """
407        try:
408            yawIndex = "%s/%s" % (self.__yawCoef + 1, self.yawNbPicts)
409        except TypeError:
410            yawIndex = str(self.__yawCoef)
411        try:
412            pitchIndex = "%s/%s" % (self.__pitchCoef + 1, self.pitchNbPicts)
413        except TypeError:
414            pitchIndex = str(self.__pitchCoef)
415        yawPos, pitchPos = self.hardware.readPosition()
416        return {'yawPos': yawPos, 'pitchPos': pitchPos,
417                'yawIndex': yawIndex, 'pitchIndex': pitchIndex,
418                'progress': self.__progress, 'sequence': self.__sequence}
419
420    def isShooting(self):
421        """ Test if shooting is running.
422
423        @return: True if shooting is running, False otherwise
424        @rtype: bool
425        """
426        return self.__running
427
428    def suspend(self):
429        """ Suspend execution of pano shooting.
430        """
431        Logger().trace("Shooting.suspend()")
432        self.__suspend = True
433
434    def isSuspended(self):
435        """ Test if shotting is suspended.
436
437        @return: True if shooting is suspended, False otherwise
438        @rtype: bool
439        """
440        return self.__suspend
441
442    def resume(self):
443        """ Resume  execution of shooting.
444        """
445        Logger().trace("Shooting.resume()")
446        self.__suspend = False
447
448    def stop(self):
449        """ Cancel execution of shooting.
450        """
451        Logger().trace("Shooting.stop()")
452        self.__stop = True
453        self.__suspend = False
454        self.hardware.stopAxis()
455
456    def shutdown(self):
457        """ Cleanly terminate the model.
458
459        Save values to preferences.
460        """
461        Logger().trace("Shooting.shutdown()")
462        self.realHardware.shutdown()
463        self.simulatedHardware.shutdown()
464        self.camera.shutdown()
Note: See TracBrowser for help on using the repository browser.