Exemplo n.º 1
0
 def __init__(self, settings, database, project=None):
     super(PlotsMath, self).__init__()
     self.ui = Ui_PlotsMath()
     self.ui.setupUi(self)
     self.settings = settings
     self.project=project
     self.plot = QtCommons.nestWidget(self.ui.plot, QMathPlotWidget())
     self.reference_dialog = ReferenceSpectraDialog(database)
     self.reference_dialog.fits_picked.connect(self.open_fits)
     self.toolbar = QToolBar('Instrument Response Toolbar')
     open_btn = QtCommons.addToolbarPopup(self.toolbar, text="Open...", icon_file=':/new_open_20')
     open_file_action = open_btn.menu().addAction('FITS file')
     open_btn.menu().addAction('Reference library', self.reference_dialog.show)
     self.blackbody_menu = blackbody.BlackBodyAction(self.blackbody, open_btn.menu())
     
     if project:
         save_result = QtCommons.addToolbarPopup(self.toolbar, text='Save', icon_file=':/save_20')
         save_result.menu().addAction('As File', lambda: QtCommons.save_file('Save Operation Result...', FITS_EXTS, lambda f: self.save(f[0]), project.path))
         save_result.menu().addAction('As Instrument Response', self.save_project_instrument_response)
         open_file_action.triggered.connect(lambda: QtCommons.open_file('Open FITS Spectrum',FITS_EXTS, lambda f: self.open_fits(f[0]), project.path))
     else:
         open_file_action.triggered.connect(lambda: open_file_sticky('Open FITS Spectrum',FITS_EXTS, lambda f: self.open_fits(f[0]), self.settings, CALIBRATED_PROFILE, [RAW_PROFILE]))
         self.toolbar.addAction(QIcon(':/save_20'), 'Save', lambda: save_file_sticky('Save Operation Result...', 'FITS file (.fit)', lambda f: self.save(f[0]), self.settings, MATH_OPERATION, [CALIBRATED_PROFILE]))
         
     self.toolbar.addAction('Set operand', self.set_operand)
     self.toolbar.addSeparator()
     self.toolbar.addAction(self.ui.actionZoom)
     self.ui.actionZoom.triggered.connect(self.start_zoom)
     self.toolbar.addAction(self.ui.actionReset_Zoom)
     self.ui.actionReset_Zoom.triggered.connect(self.reset_zoom)
     self.toolbar.addSeparator()
     self.operands_model = QStandardItemModel()
     self.ui.operands_listview.setModel(self.operands_model)
     remove_btn = QtCommons.addToolbarPopup(self.toolbar, text='Remove...')
     remove_btn.menu().addAction(self.ui.actionSelectPointsToRemove)
     remove_btn.menu().addAction("Before point", lambda: spectrum_trim_dialog(self.spectrum, 'before', self.plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo))
     remove_btn.menu().addAction("After point", lambda: spectrum_trim_dialog(self.spectrum, 'after', self.plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo))
     self.ui.clear_operands.clicked.connect(self.operands_model.clear)
     self.ui.remove_operand.clicked.connect(lambda: self.operands_model.removeRows(self.ui.operands_listview.selectionModel().selectedRows()[0].row(), 1))
         
     self.operands_model.rowsInserted.connect(lambda: self.ui.clear_operands.setEnabled(self.operands_model.rowCount() > 0) )
     self.operands_model.rowsRemoved.connect(lambda: self.ui.clear_operands.setEnabled(self.operands_model.rowCount() > 0) )
     self.ui.operands_listview.selectionModel().selectionChanged.connect(lambda s, u: self.ui.remove_operand.setEnabled(len(s)))
     self.ui.actionSelectPointsToRemove.triggered.connect(self.pick_rm_points)
     self.undo = Undo(None, self.draw)
     self.undo.add_actions(self.toolbar)
     self.ui.spline_factor.valueChanged.connect(self.factor_valueChanged)
     self.ui.spline_degrees.valueChanged.connect(lambda v: self.draw())
     self.ui.spline_factor_auto.toggled.connect(lambda v: self.draw())
     self.ui.spline_factor_auto.toggled.connect(lambda v: self.ui.spline_factor.setEnabled(not v))
     self.ui.execute.clicked.connect(self.execute_operation)
     self.plot.figure.tight_layout()
Exemplo n.º 2
0
 def __init__(self):
     # Default size
     self._width = _WORLD_WIDTH
     self._height = _WORLD_HEIGHT
     self._depth = _WORLD_DEPTH
     # Our undo buffer
     self._undo = Undo()
     # Init data
     self._initialise_data()
     # Callback when our data changes
     self.notify_changed = None
     # Ambient occlusion type effect
     self._occlusion = True
Exemplo n.º 3
0
 def __init__(self):
     # Default size
     self._width = _WORLD_WIDTH
     self._height = _WORLD_HEIGHT
     self._depth = _WORLD_DEPTH
     # Our undo buffer
     self._undo = Undo()
     # Init data
     self._initialise_data()
     # Callback when our data changes
     self.notify_changed = None
     # Ambient occlusion type effect
     self._occlusion = True
Exemplo n.º 4
0
class VoxelData(object):

    # Constants for referring to axis
    X_AXIS = 1
    Y_AXIS = 2
    Z_AXIS = 3

    # World dimension properties
    @property
    def width(self):
        return self._width

    @property
    def height(self):
        return self._height

    @property
    def depth(self):
        return self._depth

    @property
    def changed(self):
        return self._changed

    @changed.setter
    def changed(self, value):
        if value and not self._changed:
            # Let whoever is watching us know about the change
            self._changed = value
            if self.notify_changed:
                self.Color()
        self._changed = value

    @property
    def occlusion(self):
        return self._occlusion

    @occlusion.setter
    def occlusion(self, value):
        self._occlusion = value

    def __init__(self):
        # Default size
        self._width = _WORLD_WIDTH
        self._height = _WORLD_HEIGHT
        self._depth = _WORLD_DEPTH
        # Our undo buffer
        self._undo = Undo()
        # Init data
        self._initialise_data()
        # Callback when our data changes
        self.notify_changed = None
        # Ambient occlusion type effect
        self._occlusion = True
        self._undoFillNew = []
        self._undoFillOld = []

    # Initialise our data
    def _initialise_data(self):
        # Our scene data
        self._data = self.blank_data()
        # Create empty selection
        self._selection = set()
        # Our cache of non-empty voxels (coordinate groups)
        self._cache = []
        # Flag indicating if our data has changed
        self._changed = False
        # Reset undo buffer
        self._undo.clear()
        # Animation
        self._frame_count = 1
        self._current_frame = 0
        self._frames = [self._data]

    # Return an empty voxel space
    def blank_data(self):
        return [[[0 for _ in xrange(self.depth)] for _ in xrange(self.height)]
                for _ in xrange(self.width)]

    def is_valid_bounds(self, x, y, z):
        return x >= 0 and x < self.width and y >= 0 and y < self.height and z >= 0 and z < self.depth

    # Return the number of animation frames
    def get_frame_count(self):
        return self._frame_count

    # Change to the given frame
    def select_frame(self, frame_number):
        # Sanity
        if frame_number < 0 or frame_number >= self._frame_count:
            return
        # Make sure we really have a pointer to the current data
        self._frames[self._current_frame] = self._data
        # Change to new frame
        self._data = self._frames[frame_number]
        self._current_frame = frame_number
        self._undo.frame = self._current_frame
        self._cache_rebuild()
        self.changed = True
        self.clear_selection()

    def insert_frame(self, index, copy_current=True):
        if copy_current:
            data = self.get_data()
        else:
            data = self.blank_data()
        # If current frame is at the position of the new frame
        # We must move out of the way.
        if self._current_frame == index:
            self.select_frame(index - 1)
        self._frames.insert(index, data)
        self._undo.add_frame(index)
        self._frame_count += 1
        self.select_frame(index)

    # Add a new frame by copying the current one
    def add_frame(self, copy_current=True):
        if copy_current:
            data = self.get_data()
        else:
            data = self.blank_data()

        self._frames.insert(self._current_frame + 1, data)
        self._undo.add_frame(self._current_frame + 1)
        self._frame_count += 1
        self.select_frame(self._current_frame + 1)

    def copy_to_current(self, index):
        data = self._frames[index - 1]
        self.set_data(data)

    # Delete the current frame
    def delete_frame(self):
        # Sanity - we can't have no frames at all
        if self._frame_count <= 1:
            return
        # Remember the frame we want to delete
        killframe = self._current_frame
        # Select a different frame
        self.select_previous_frame()
        # Remove the old frame
        del self._frames[killframe]
        self._undo.delete_frame(killframe)
        self._frame_count -= 1
        # If we only have one frame left, must be first frame
        if self._frame_count == 1:
            self._current_frame = 0
        # If we wrapped around, fix the frame pointer
        if self._current_frame > killframe:
            self._current_frame -= 1

    # Change to the next frame (with wrap)
    def select_next_frame(self):
        nextframe = self._current_frame + 1
        if nextframe >= self._frame_count:
            nextframe = 0
        self.select_frame(nextframe)

    # Change to the previous frame (with wrap)
    def select_previous_frame(self):
        prevframe = self._current_frame - 1
        if prevframe < 0:
            prevframe = self._frame_count - 1
        self.select_frame(prevframe)

    # Get current frame number
    def get_frame_number(self):
        return self._current_frame

    def is_free(self, data):
        for x, y, z, col in data:
            if self.get(x, y, z) != 0:
                return False
        return True

    # Set a voxel to the given state
    def set(self, x, y, z, state, undo=True, fill=0):
        # If this looks like a QT Color instance, convert it
        if hasattr(state, "getRgb"):
            c = state.getRgb()
            state = c[0] << 24 | c[1] << 16 | c[2] << 8 | 0xff

        # Check bounds
        if not self.is_valid_bounds(x, y, z):
            return False
        # Add to undo
        if undo:
            if fill > 0:
                self._undoFillOld.append((x, y, z, self._data[x][y][z]))
                self._undoFillNew.append((x, y, z, state))
                if fill == 2:
                    self.completeUndoFill()
            else:
                self._undo.add(
                    UndoItem(Undo.SET_VOXEL, (x, y, z, self._data[x][y][z]),
                             (x, y, z, state)))
        # Set the voxel
        self._data[x][y][z] = state
        if state != EMPTY:
            if (x, y, z) not in self._cache:
                self._cache.append((x, y, z))
        else:
            if (x, y, z) in self._cache:
                self._cache.remove((x, y, z))
        self.changed = True
        return True

    def completeUndoFill(self):
        self._undo.add(
            UndoItem(Undo.FILL, self._undoFillOld, self._undoFillNew))
        self._undoFillOld = []
        self._undoFillNew = []

    def select(self, x, y, z):
        self._selection.add((x, y, z))

    def deselect(self, x, y, z):
        if (x, y, z) in self._selection:
            self._selection.remove((x, y, z))

    def is_selected(self, x, y, z):
        return (x, y, z) in self._selection

    def clear_selection(self):
        self._selection.clear()

    # Get the state of the given voxel
    def get(self, x, y, z):
        if not self.is_valid_bounds(x, y, z):
            return EMPTY
        return self._data[x][y][z]

    # Return a copy of the voxel data
    def get_data(self):
        return copy.deepcopy(self._data)

    # Set all of our data at once
    def set_data(self, data):
        self._data = copy.deepcopy(data)
        self._cache_rebuild()
        self.changed = True

    # Clear our voxel data
    def clear(self):
        self._initialise_data()

    # Return full vertex list
    def get_vertices(self):
        vertices = []
        colors = []
        color_ids = []
        normals = []
        uvs = []
        for x, y, z in self._cache:
            v, c, n, cid, uv = self._get_voxel_vertices(x, y, z)
            vertices += v
            if (x, y, z) in self._selection:
                clen = len(c) / 3
                c = []
                for i in range(clen):
                    c.append(255)
                    c.append(0)
                    c.append(255)
            colors += c
            normals += n
            color_ids += cid
            uvs += uv
        return (vertices, colors, normals, color_ids, uvs)

    # Called to notify us that our data has been saved. i.e. we can set
    # our "changed" status back to False.
    def saved(self):
        self.changed = False

    # Count the number of non-empty voxels from the list of coordinates
    def _count_voxels(self, coordinates):
        count = 0
        for x, y, z in coordinates:
            if self.get(x, y, z) != EMPTY:
                count += 1
        return count

    # Return the verticies for the given voxel. We center our vertices at the origin
    def _get_voxel_vertices(self, x, y, z):
        vertices = []
        colors = []
        normals = []
        color_ids = []
        uvs = []

        # Remember voxel coordinates
        vx, vy, vz = x, y, z

        # Determine if we have filled voxels around us
        front = self.get(x, y, z - 1) == EMPTY
        left = self.get(x - 1, y, z) == EMPTY
        right = self.get(x + 1, y, z) == EMPTY
        top = self.get(x, y + 1, z) == EMPTY
        back = self.get(x, y, z + 1) == EMPTY
        bottom = self.get(x, y - 1, z) == EMPTY

        # Get our color
        c = self.get(x, y, z)
        r = (c & 0xff000000) >> 24
        g = (c & 0xff0000) >> 16
        b = (c & 0xff00) >> 8
        # Calculate shades for our 4 occlusion levels
        shades = []
        for c in range(5):
            shades.append((int(r * math.pow(OCCLUSION, c)),
                           int(g * math.pow(OCCLUSION, c)),
                           int(b * math.pow(OCCLUSION, c))))

        # Encode our voxel space coordinates as colors, used for face selection
        # We use 7 bits per coordinate and the bottom 3 bits for face:
        #   0 - front
        #   1 - top
        #   2 - left
        #   3 - right
        #   4 - back
        #   5 - bottom
        voxel_id = (x & 0x7f) << 17 | (y & 0x7f) << 10 | (z & 0x7f) << 3
        id_r = (voxel_id & 0xff0000) >> 16
        id_g = (voxel_id & 0xff00) >> 8
        id_b = (voxel_id & 0xff)

        # Adjust coordinates to the origin
        x, y, z = self.voxel_to_world(x, y, z)

        # Front face
        if front:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx, vy + 1, vz - 1) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx - 1, vy, vz - 1) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx + 1, vy, vz - 1) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx, vy - 1, vz - 1) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx - 1, vy - 1, vz - 1) != EMPTY:
                    occ1 += 1
                if self.get(vx - 1, vy + 1, vz - 1) != EMPTY:
                    occ2 += 1
                if self.get(vx + 1, vy - 1, vz - 1) != EMPTY:
                    occ3 += 1
                if self.get(vx + 1, vy + 1, vz - 1) != EMPTY:
                    occ4 += 1
            vertices += (x, y, z)
            colors += shades[occ1]
            vertices += (x, y + 1, z)
            colors += shades[occ2]
            vertices += (x + 1, y, z)
            colors += shades[occ3]
            vertices += (x + 1, y, z)
            colors += shades[occ3]
            vertices += (x, y + 1, z)
            colors += shades[occ2]
            vertices += (x + 1, y + 1, z)
            colors += shades[occ4]
            uvs += (0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1)
            normals += (0, 0, 1) * 6
            color_ids += (id_r, id_g, id_b) * 6
        # Top face
        if top:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx, vy + 1, vz + 1) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx - 1, vy + 1, vz) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx + 1, vy + 1, vz) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx, vy + 1, vz - 1) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx - 1, vy + 1, vz - 1) != EMPTY:
                    occ1 += 1
                if self.get(vx + 1, vy + 1, vz - 1) != EMPTY:
                    occ3 += 1
                if self.get(vx + 1, vy + 1, vz + 1) != EMPTY:
                    occ4 += 1
                if self.get(vx - 1, vy + 1, vz + 1) != EMPTY:
                    occ2 += 1
            vertices += (x, y + 1, z)
            colors += shades[occ1]
            vertices += (x, y + 1, z - 1)
            colors += shades[occ2]
            vertices += (x + 1, y + 1, z)
            colors += shades[occ3]
            vertices += (x + 1, y + 1, z)
            colors += shades[occ3]
            vertices += (x, y + 1, z - 1)
            colors += shades[occ2]
            vertices += (x + 1, y + 1, z - 1)
            colors += shades[occ4]
            uvs += (0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1)
            normals += (0, 1, 0) * 6
            color_ids += (id_r, id_g, id_b | 1) * 6
        # Right face
        if right:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx + 1, vy + 1, vz) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx + 1, vy, vz - 1) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx + 1, vy, vz + 1) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx + 1, vy - 1, vz) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx + 1, vy - 1, vz - 1) != EMPTY:
                    occ1 += 1
                if self.get(vx + 1, vy + 1, vz - 1) != EMPTY:
                    occ2 += 1
                if self.get(vx + 1, vy - 1, vz + 1) != EMPTY:
                    occ3 += 1
                if self.get(vx + 1, vy + 1, vz + 1) != EMPTY:
                    occ4 += 1
            vertices += (x + 1, y, z)
            colors += shades[occ1]
            vertices += (x + 1, y + 1, z)
            colors += shades[occ2]
            vertices += (x + 1, y, z - 1)
            colors += shades[occ3]
            vertices += (x + 1, y, z - 1)
            colors += shades[occ3]
            vertices += (x + 1, y + 1, z)
            colors += shades[occ2]
            vertices += (x + 1, y + 1, z - 1)
            colors += shades[occ4]
            uvs += (0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1)
            normals += (1, 0, 0) * 6
            color_ids += (id_r, id_g, id_b | 3) * 6
        # Left face
        if left:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx - 1, vy + 1, vz) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx - 1, vy, vz + 1) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx - 1, vy, vz - 1) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx - 1, vy - 1, vz) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx - 1, vy - 1, vz + 1) != EMPTY:
                    occ1 += 1
                if self.get(vx - 1, vy + 1, vz + 1) != EMPTY:
                    occ2 += 1
                if self.get(vx - 1, vy - 1, vz - 1) != EMPTY:
                    occ3 += 1
                if self.get(vx - 1, vy + 1, vz - 1) != EMPTY:
                    occ4 += 1
            vertices += (x, y, z - 1)
            colors += shades[occ1]
            vertices += (x, y + 1, z - 1)
            colors += shades[occ2]
            vertices += (x, y, z)
            colors += shades[occ3]
            vertices += (x, y, z)
            colors += shades[occ3]
            vertices += (x, y + 1, z - 1)
            colors += shades[occ2]
            vertices += (x, y + 1, z)
            colors += shades[occ4]
            uvs += (0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1)
            normals += (-1, 0, 0) * 6
            color_ids += (id_r, id_g, id_b | 2) * 6
        # Back face
        if back:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx, vy + 1, vz + 1) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx + 1, vy, vz + 1) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx - 1, vy, vz + 1) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx, vy - 1, vz + 1) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx + 1, vy - 1, vz + 1) != EMPTY:
                    occ1 += 1
                if self.get(vx + 1, vy + 1, vz + 1) != EMPTY:
                    occ2 += 1
                if self.get(vx - 1, vy - 1, vz + 1) != EMPTY:
                    occ3 += 1
                if self.get(vx - 1, vy + 1, vz + 1) != EMPTY:
                    occ4 += 1
            vertices += (x + 1, y, z - 1)
            colors += shades[occ1]
            vertices += (x + 1, y + 1, z - 1)
            colors += shades[occ2]
            vertices += (x, y, z - 1)
            colors += shades[occ3]
            vertices += (x, y, z - 1)
            colors += shades[occ3]
            vertices += (x + 1, y + 1, z - 1)
            colors += shades[occ2]
            vertices += (x, y + 1, z - 1)
            colors += shades[occ4]
            uvs += (0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1)
            normals += (0, 0, -1) * 6
            color_ids += (id_r, id_g, id_b | 4) * 6
        # Bottom face
        if bottom:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx, vy - 1, vz - 1) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx - 1, vy - 1, vz) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx + 1, vy - 1, vz) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx, vy - 1, vz + 1) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx - 1, vy - 1, vz + 1) != EMPTY:
                    occ1 += 1
                if self.get(vx - 1, vy - 1, vz - 1) != EMPTY:
                    occ2 += 1
                if self.get(vx + 1, vy - 1, vz + 1) != EMPTY:
                    occ3 += 1
                if self.get(vx + 1, vy - 1, vz - 1) != EMPTY:
                    occ4 += 1
            vertices += (x, y, z - 1)
            colors += shades[occ1]
            vertices += (x, y, z)
            colors += shades[occ2]
            vertices += (x + 1, y, z - 1)
            colors += shades[occ3]
            vertices += (x + 1, y, z - 1)
            colors += shades[occ3]
            vertices += (x, y, z)
            colors += shades[occ2]
            vertices += (x + 1, y, z)
            colors += shades[occ4]
            uvs += (0, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 1)
            normals += (0, -1, 0) * 6
            color_ids += (id_r, id_g, id_b | 5) * 6

        return (vertices, colors, normals, color_ids, uvs)

    # Return vertices for a floor grid
    def get_grid_vertices(self):
        grid = []
        # builds the Y_plane
        for z in xrange(self.depth + 1):
            gx, gy, gz = self.voxel_to_world(0, 0, z)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(self.width, 0, z)
            grid += (gx, gy, gz)
        for x in xrange(self.width + 1):
            gx, gy, gz = self.voxel_to_world(x, 0, 0)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(x, 0, self.depth)
            grid += (gx, gy, gz)
        # builds the Z_plane
        for x in xrange(self.width + 1):
            gx, gy, gz = self.voxel_to_world(x, 0, self.depth)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(x, self.height, self.depth)
            grid += (gx, gy, gz)
        for y in xrange(self.height + 1):
            gx, gy, gz = self.voxel_to_world(0, y, self.depth)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(self.width, y, self.depth)
            grid += (gx, gy, gz)
        # builds the X_plane
        for y in xrange(self.height + 1):
            gx, gy, gz = self.voxel_to_world(0, y, 0)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(0, y, self.depth)
            grid += (gx, gy, gz)
        for z in xrange(self.depth + 1):
            gx, gy, gz = self.voxel_to_world(0, 0, z)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(0, self.height, z)
            grid += (gx, gy, gz)
        return grid

    # Convert voxel space coordinates to world space
    def voxel_to_world(self, x, y, z):
        x = (x - self.width // 2) - 0.5
        y = (y - self.height // 2) - 0.5
        z = (z - self.depth // 2) - 0.5
        z = -z
        return x, y, z

    # Convert world space coordinates to voxel space
    def world_to_voxel(self, x, y, z):
        x = (x + self.width // 2) + 0.5
        y = (y + self.height // 2) + 0.5
        z = (z - self.depth // 2) - 0.5
        z = -z
        return x, y, z

    # Rebuild our cache
    def _cache_rebuild(self):
        self._cache = []
        for x in range(self.width):
            for z in range(self.depth):
                for y in range(self.height):
                    if self._data[x][y][z] != EMPTY:
                        self._cache.append((x, y, z))

    # Calculate the actual bounding box of the model in voxel space
    # Consider all animation frames
    def get_bounding_box(self):
        minx = 999
        miny = 999
        minz = 999
        maxx = -999
        maxy = -999
        maxz = -999
        for data in self._frames:
            for x in range(self.width):
                for z in range(self.depth):
                    for y in range(self.height):
                        if data[x][y][z] != EMPTY:
                            if x < minx:
                                minx = x
                            if x > maxx:
                                maxx = x
                            if y < miny:
                                miny = y
                            if y > maxy:
                                maxy = y
                            if z < minz:
                                minz = z
                            if z > maxz:
                                maxz = z
        width = (maxx - minx) + 1
        height = (maxy - miny) + 1
        depth = (maxz - minz) + 1
        return minx, miny, minz, width, height, depth

    # Resize the voxel space. If no dimensions given, adjust to bounding box.
    # We offset all voxels on all axis by the given amount.
    # Resize all animation frames
    def resize(self, width=None, height=None, depth=None, shift=0):
        # Reset undo buffer
        self._undo.clear()
        # No dimensions, use bounding box
        mx, my, mz, cwidth, cheight, cdepth = self.get_bounding_box()
        if not width:
            width, height, depth = cwidth, cheight, cdepth
        for i, frame in enumerate(self._frames):
            # Create new data structure of the required size
            data = [[[0 for _ in xrange(depth)] for _ in xrange(height)]
                    for _ in xrange(width)]
            # Adjust ranges
            movewidth = min(width, cwidth)
            moveheight = min(height, cheight)
            movedepth = min(depth, cdepth)
            # Calculate translation
            dx = (0 - mx) + shift
            dy = (0 - my) + shift
            dz = (0 - mz) + shift
            # Copy data over at new location
            for x in xrange(mx, mx + movewidth):
                for y in xrange(my, my + moveheight):
                    for z in xrange(mz, mz + movedepth):
                        data[x + dx][y + dy][z + dz] = frame[x][y][z]
            self._frames[i] = data
        self._data = self._frames[self._current_frame]
        # Set new dimensions
        self._width = width
        self._height = height
        self._depth = depth
        # Rebuild our cache
        self._cache_rebuild()
        self.changed = True

    # Rotate voxels in voxel space 90 degrees
    def rotate_about_axis(self, axis):
        # Reset undo buffer
        self._undo.clear()

        if axis == self.Y_AXIS:
            width = self.depth  # note swap
            height = self.height
            depth = self.width
        elif axis == self.X_AXIS:
            width = self.width
            height = self.depth
            depth = self.height
        elif axis == self.Z_AXIS:
            width = self.height
            height = self.width
            depth = self.depth

        for i, frame in enumerate(self._frames):

            # Create new temporary data structure
            data = [[[0 for _ in xrange(depth)] for _ in xrange(height)]
                    for _ in xrange(width)]

            # Copy data over at new location
            for tx in xrange(0, self.width):
                for ty in xrange(0, self.height):
                    for tz in xrange(0, self.depth):
                        if axis == self.Y_AXIS:
                            dx = (-tz) - 1
                            dy = ty
                            dz = tx
                        elif axis == self.X_AXIS:
                            dx = tx
                            dy = (-tz) - 1
                            dz = ty
                        elif axis == self.Z_AXIS:
                            dx = ty
                            dy = (-tx) - 1
                            dz = tz
                        data[dx][dy][dz] = frame[tx][ty][tz]
            self._frames[i] = data

        self._width = width
        self._height = height
        self._depth = depth

        self._data = self._frames[self._current_frame]
        # Rebuild our cache
        self._cache_rebuild()
        self.changed = True

    # Mirror voxels in a axis
    def mirror_in_axis(self, axis):
        # Reset undo buffer
        self._undo.clear()

        for i, frame in enumerate(self._frames):

            # Create new temporary data structure
            data = [[[0 for _ in xrange(self.depth)]
                     for _ in xrange(self.height)] for _ in xrange(self.width)]

            # Copy data over at new location
            for tx in xrange(0, self.width):
                for ty in xrange(0, self.height):
                    for tz in xrange(0, self.depth):
                        if axis == self.Y_AXIS:
                            dx = tx
                            dy = (-ty) - 1
                            dz = tz
                        elif axis == self.X_AXIS:
                            dx = (-tx) - 1
                            dy = ty
                            dz = tz
                        elif axis == self.Z_AXIS:
                            dx = tx
                            dy = ty
                            dz = (-tz) - 1
                        data[dx][dy][dz] = frame[tx][ty][tz]
            self._frames[i] = data

        self._data = self._frames[self._current_frame]
        # Rebuild our cache
        self._cache_rebuild()
        self.changed = True

    # Translate the voxel data.
    def translate(self, x, y, z, undo=True):
        # Sanity
        if x == 0 and y == 0 and z == 0:
            return

        # Add to undo
        if undo:
            self._undo.add(UndoItem(Undo.TRANSLATE, (-x, -y, -z), (x, y, z)))

        # Create new temporary data structure
        data = [[[0 for _ in xrange(self.depth)] for _ in xrange(self.height)]
                for _ in xrange(self.width)]
        # Copy data over at new location
        for tx in xrange(0, self.width):
            for ty in xrange(0, self.height):
                for tz in xrange(0, self.depth):
                    dx = (tx + x) % self.width
                    dy = (ty + y) % self.height
                    dz = (tz + z) % self.depth
                    data[dx][dy][dz] = self._data[tx][ty][tz]
        self._data = data
        self._frames[self._current_frame] = self._data
        # Rebuild our cache
        self._cache_rebuild()
        self.changed = True

    # Undo previous operation
    def undo(self):
        self.clear_selection()
        op = self._undo.undo()
        # Voxel edit
        if op and op.operation == Undo.SET_VOXEL:
            data = op.olddata
            self.set(data[0], data[1], data[2], data[3], False)
        elif op and op.operation == Undo.FILL:
            d = op.olddata
            for data in d:
                self.set(data[0], data[1], data[2], data[3], False)
        # Translation
        elif op and op.operation == Undo.TRANSLATE:
            data = op.olddata
            self.translate(data[0], data[1], data[2], False)

    # Redo an undone operation
    def redo(self):
        op = self._undo.redo()
        # Voxel edit
        if op and op.operation == Undo.SET_VOXEL:
            data = op.newdata
            self.set(data[0], data[1], data[2], data[3], False)
        elif op and op.operation == Undo.FILL:
            d = op.newdata
            for data in d:
                self.set(data[0], data[1], data[2], data[3], False)
        # Translation
        elif op and op.operation == Undo.TRANSLATE:
            data = op.newdata
            self.translate(data[0], data[1], data[2], False)

    # Enable/Disable undo buffer
    def disable_undo(self):
        self._undo.enabled = False

    def enable_undo(self):
        self._undo.enabled = True
Exemplo n.º 5
0
    def __init__(self, fits_file, settings, database, project=None):
        super(FinishSpectrum, self).__init__()
        self.settings = settings
        self.ui = Ui_FinishSpectrum()
        self.ui.setupUi(self)
        self.profile_line = None
        self.project = project
        self.fits_spectrum = FitsSpectrum(fits_file)
        self.undo = Undo(self.fits_spectrum.spectrum, self.draw)
        try:
            fits_file.index_of('ORIGINAL_DATA')
        except KeyError:
            hdu = fits.ImageHDU(data = fits_file[0].data, header = fits_file[0].header, name='ORIGINAL_DATA')
            fits_file.append(hdu)

        self.fits_spectrum.spectrum.normalize_to_max()
        self.spectrum = self.fits_spectrum.spectrum
        self.spectrum_plot = QtCommons.nestWidget(self.ui.plot, QMathPlotWidget())
        self.spectrum_plot.mouse_moved.connect(Instances.MainWindow.print_coordinates)

        self.split_view()
        self.toolbar = QToolBar('Finish Spectrum Toolbar')
        if project:
            instrument_response_action = QtCommons.addToolbarPopup(self.toolbar, "Instrument Response")
            instrument_response_action.menu().addAction('From FITS file...', lambda: open_file_sticky('Open Instrument Response Profile', FITS_EXTS, lambda f: self.instrument_response(f[0]), settings, MATH_OPERATION, [RAW_PROFILE]))
            for instrument_response in project.get_instrument_responses():
                print("Adding instrument response {}".format(instrument_response))
                instrument_response_action.menu().addAction(os.path.basename(instrument_response[1]), lambda: self.instrument_response(instrument_response[1]))
        else:
            self.toolbar.addAction('Instrument Response', lambda: open_file_sticky('Open Instrument Response Profile', FITS_EXTS, lambda f: self.instrument_response(f[0]), settings, MATH_OPERATION, [RAW_PROFILE]))
        self.toolbar.addAction("Zoom", lambda: self.spectrum_plot.select_zoom(self.profile_plot.axes))
        self.toolbar.addAction("Reset Zoom", lambda: self.spectrum_plot.reset_zoom(self.spectrum.wavelengths, self.spectrum.fluxes.min(), self.spectrum.fluxes.max(), self.profile_plot.axes))
        remove_action = QtCommons.addToolbarPopup(self.toolbar, "Remove")
        remove_action.menu().addAction("Before point", lambda: spectrum_trim_dialog(self.spectrum, 'before', self.profile_plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo))
        remove_action.menu().addAction("After point", lambda: spectrum_trim_dialog(self.spectrum, 'after', self.profile_plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo))
        self.undo.add_actions(self.toolbar)
        self.toolbar.addSeparator()
        
        self.reference_spectra_dialog = ReferenceSpectraDialog(database, self.fits_spectrum.spectrum)
        self.reference_spectra_dialog.setup_menu(self.toolbar, self.profile_plot.axes, settings)

        lines_menu = QtCommons.addToolbarPopup(self.toolbar, "Spectral Lines..")
        lines_menu.menu().addAction('Lines Database', lambda: self.lines_dialog.show())
        lines_menu.menu().addAction('Custom line', self.add_custom_line)
        labels_action = QtCommons.addToolbarPopup(self.toolbar, "Labels..")
        self.object_properties = ObjectProperties(fits_file, project=project)
        labels_action.menu().addAction('Title', self.add_title)
        if self.object_properties:
            labels_action.menu().addAction('Information from FITS file', self.add_fits_information_label)
        labels_action.menu().addAction('Custom', self.add_label)
        

        self.object_properties_dialog = ObjectPropertiesDialog(settings, self.object_properties)
        self.toolbar.addAction("Object properties", self.object_properties_dialog.show)

        self.labels, self.lines = [], []
        for label in self.fits_spectrum.labels():
            self.add_label(text=label['text'], coords=label['coords'], type=label['type'], fontsize=label['fontsize'])
        
        self.toolbar.addSeparator()
        if project:
            self.toolbar.addAction(QIcon(':/image_20'), "Export Image...", lambda: QtCommons.save_file('Export plot to image', 'PNG (*.png);;PDF (*.pdf);;PostScript (*.ps);;SVG (*.svg)', lambda f: self.save_image(f[0]), project.directory_path(Project.EXPORTED_IMAGES)))
            self.toolbar.addAction(QIcon(':/save_20'), 'Save', self.save_finished_in_project)
        else:
            self.toolbar.addAction(QIcon(':/image_20'), "Export Image...", lambda: save_file_sticky('Export plot to image', 'PNG (*.png);;PDF (*.pdf);;PostScript (*.ps);;SVG (*.svg)', lambda f: self.save_image(f[0]), self.settings, EXPORT_IMAGES, [CALIBRATED_PROFILE]))
            self.toolbar.addAction(QIcon(':/save_20'), 'Save', lambda: save_file_sticky('Save plot...', 'FITS file (.fit)', lambda f: self.__save(f[0]), self.settings, CALIBRATED_PROFILE))
            
        self.lines_dialog = LinesDialog(database, settings, self.spectrum_plot, self.profile_plot.axes)
        self.lines_dialog.lines.connect(self.add_lines)
        for line in self.fits_spectrum.lines_labels():
            self.lines.append(ReferenceLine(line['text'], line['wavelength'], self.profile_plot.axes, lambda line: self.lines.remove(line), show_wavelength=line['display_wavelength'], fontsize=line['fontsize'], position=line['position']))
Exemplo n.º 6
0
class FinishSpectrum(QWidget):
    def __init__(self, fits_file, settings, database, project=None):
        super(FinishSpectrum, self).__init__()
        self.settings = settings
        self.ui = Ui_FinishSpectrum()
        self.ui.setupUi(self)
        self.profile_line = None
        self.project = project
        self.fits_spectrum = FitsSpectrum(fits_file)
        self.undo = Undo(self.fits_spectrum.spectrum, self.draw)
        try:
            fits_file.index_of('ORIGINAL_DATA')
        except KeyError:
            hdu = fits.ImageHDU(data = fits_file[0].data, header = fits_file[0].header, name='ORIGINAL_DATA')
            fits_file.append(hdu)

        self.fits_spectrum.spectrum.normalize_to_max()
        self.spectrum = self.fits_spectrum.spectrum
        self.spectrum_plot = QtCommons.nestWidget(self.ui.plot, QMathPlotWidget())
        self.spectrum_plot.mouse_moved.connect(Instances.MainWindow.print_coordinates)

        self.split_view()
        self.toolbar = QToolBar('Finish Spectrum Toolbar')
        if project:
            instrument_response_action = QtCommons.addToolbarPopup(self.toolbar, "Instrument Response")
            instrument_response_action.menu().addAction('From FITS file...', lambda: open_file_sticky('Open Instrument Response Profile', FITS_EXTS, lambda f: self.instrument_response(f[0]), settings, MATH_OPERATION, [RAW_PROFILE]))
            for instrument_response in project.get_instrument_responses():
                print("Adding instrument response {}".format(instrument_response))
                instrument_response_action.menu().addAction(os.path.basename(instrument_response[1]), lambda: self.instrument_response(instrument_response[1]))
        else:
            self.toolbar.addAction('Instrument Response', lambda: open_file_sticky('Open Instrument Response Profile', FITS_EXTS, lambda f: self.instrument_response(f[0]), settings, MATH_OPERATION, [RAW_PROFILE]))
        self.toolbar.addAction("Zoom", lambda: self.spectrum_plot.select_zoom(self.profile_plot.axes))
        self.toolbar.addAction("Reset Zoom", lambda: self.spectrum_plot.reset_zoom(self.spectrum.wavelengths, self.spectrum.fluxes.min(), self.spectrum.fluxes.max(), self.profile_plot.axes))
        remove_action = QtCommons.addToolbarPopup(self.toolbar, "Remove")
        remove_action.menu().addAction("Before point", lambda: spectrum_trim_dialog(self.spectrum, 'before', self.profile_plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo))
        remove_action.menu().addAction("After point", lambda: spectrum_trim_dialog(self.spectrum, 'after', self.profile_plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo))
        self.undo.add_actions(self.toolbar)
        self.toolbar.addSeparator()
        
        self.reference_spectra_dialog = ReferenceSpectraDialog(database, self.fits_spectrum.spectrum)
        self.reference_spectra_dialog.setup_menu(self.toolbar, self.profile_plot.axes, settings)

        lines_menu = QtCommons.addToolbarPopup(self.toolbar, "Spectral Lines..")
        lines_menu.menu().addAction('Lines Database', lambda: self.lines_dialog.show())
        lines_menu.menu().addAction('Custom line', self.add_custom_line)
        labels_action = QtCommons.addToolbarPopup(self.toolbar, "Labels..")
        self.object_properties = ObjectProperties(fits_file, project=project)
        labels_action.menu().addAction('Title', self.add_title)
        if self.object_properties:
            labels_action.menu().addAction('Information from FITS file', self.add_fits_information_label)
        labels_action.menu().addAction('Custom', self.add_label)
        

        self.object_properties_dialog = ObjectPropertiesDialog(settings, self.object_properties)
        self.toolbar.addAction("Object properties", self.object_properties_dialog.show)

        self.labels, self.lines = [], []
        for label in self.fits_spectrum.labels():
            self.add_label(text=label['text'], coords=label['coords'], type=label['type'], fontsize=label['fontsize'])
        
        self.toolbar.addSeparator()
        if project:
            self.toolbar.addAction(QIcon(':/image_20'), "Export Image...", lambda: QtCommons.save_file('Export plot to image', 'PNG (*.png);;PDF (*.pdf);;PostScript (*.ps);;SVG (*.svg)', lambda f: self.save_image(f[0]), project.directory_path(Project.EXPORTED_IMAGES)))
            self.toolbar.addAction(QIcon(':/save_20'), 'Save', self.save_finished_in_project)
        else:
            self.toolbar.addAction(QIcon(':/image_20'), "Export Image...", lambda: save_file_sticky('Export plot to image', 'PNG (*.png);;PDF (*.pdf);;PostScript (*.ps);;SVG (*.svg)', lambda f: self.save_image(f[0]), self.settings, EXPORT_IMAGES, [CALIBRATED_PROFILE]))
            self.toolbar.addAction(QIcon(':/save_20'), 'Save', lambda: save_file_sticky('Save plot...', 'FITS file (.fit)', lambda f: self.__save(f[0]), self.settings, CALIBRATED_PROFILE))
            
        self.lines_dialog = LinesDialog(database, settings, self.spectrum_plot, self.profile_plot.axes)
        self.lines_dialog.lines.connect(self.add_lines)
        for line in self.fits_spectrum.lines_labels():
            self.lines.append(ReferenceLine(line['text'], line['wavelength'], self.profile_plot.axes, lambda line: self.lines.remove(line), show_wavelength=line['display_wavelength'], fontsize=line['fontsize'], position=line['position']))
                
    
        
    def add_custom_line(self):
        wl = QInputDialog.getDouble(self, "Custom Line", "Enter line wavelength in Å", self.fits_spectrum.spectrum.wavelengths[0],self.fits_spectrum.spectrum.wavelengths[0],self.fits_spectrum.spectrum.wavelengths[-1],3)
        if not wl[1]: return
        self.add_lines([{'name': 'Custom Line', 'lambda': wl[0]}])
        
    def add_lines(self, lines):
        for line in lines:
            self.lines.append(ReferenceLine(line['name'], line['lambda'], self.profile_plot.axes, lambda line: self.lines.remove(line)))

    
    def synthetize_img(wavelengths, fluxes):
        f_fluxes = lambda f: math.pow(f, 3/5)
        colors = [wavelength_to_rgb(w/10., f_fluxes(fluxes[i])) for i,w in enumerate(wavelengths)]
        im_height = 150
        colors = np.array(colors*im_height).reshape(im_height,len(colors),4)
        return colors, im_height
        

    def split_view(self):
        figure = self.spectrum_plot.figure
        figure.clear()
        self.gs = gridspec.GridSpec(40,1)
        self.profile_plot = figure.add_subplot(self.gs[0:-6])
        self.synthetize = figure.add_subplot(self.gs[-3:-1], sharex = self.profile_plot)
        self.synthetize.yaxis.set_visible(False)
        self.synthetize.xaxis.set_visible(False)
        self.draw()
        
    
    def draw(self):
#        self.profile_plot.clear()
        if self.profile_line:
            self.profile_line.remove()
        self.profile_line = self.profile_plot.plot(self.spectrum.wavelengths, self.spectrum.fluxes, color='blue')[0]

        self.synthetize.axes.set_axis_bgcolor('black')
        
        with ThreadPoolExecutor(max_workers=1) as executor:
            future = executor.submit(FinishSpectrum.synthetize_img, self.spectrum.wavelengths, self.spectrum.fluxes)
            future.add_done_callback(lambda f: self.synthetize.imshow(f.result()[0], extent=[self.spectrum.wavelengths[0], self.spectrum.wavelengths[-1], 0, f.result()[1]]) )

        self.profile_plot.axes.set_xlabel('wavelength (Å)')
        self.profile_plot.axes.set_ylabel('relative flux')
        self.profile_plot.axes.xaxis.set_major_locator(MaxNLocator(16)) # TODO: settings for customization?
        self.profile_plot.axes.xaxis.set_minor_locator(MaxNLocator(200))
        self.spectrum_plot.figure.canvas.draw()
        self.gs.tight_layout(self.spectrum_plot.figure)
        
    def instrument_response(self, filename):
        print("Applying instrument response {}".format(filename))
        instrument_response_file = fits.open(filename)
        instrument_response = FitsSpectrum(instrument_response_file)
        response = instrument_response.spectrum
        response.normalize_to_max()
        
        range = (max(response.wavelengths[0], self.spectrum.wavelengths[0] ), min(response.wavelengths[-1], self.spectrum.wavelengths[-1]))
        self.spectrum.cut(self.spectrum.wavelength_index(range[0]), self.spectrum.wavelength_index(range[1]))
        spline = InterpolatedUnivariateSpline(response.wavelengths, response.fluxes)
        
        response_data = [spline(x) for x in self.spectrum.wavelengths]
        self.spectrum.fluxes /= response_data
        self.spectrum.normalize_to_max()
        self.draw()
        
        
    def save_image(self, filename):
        Notification('Image {} saved in {}'.format(os.path.basename(filename), os.path.dirname(filename)), title='File Saved', type='success', timeout=5)
        self.spectrum_plot.figure.savefig(filename, bbox_inches='tight', dpi=300)

    def save_finished_in_project(self):
        self.project.add_file(Project.FINISHED_PROFILES, self.__save, self.object_properties)
        
    def __save(self, filename):
        self.fits_spectrum.save(filename, spectral_lines = self.lines, labels = self.labels)
        
    def add_title(self):
        title = self.object_properties.name if self.object_properties else 'Title - double click to edit'
        self.add_label(text=title, coords=(self.spectrum.wavelengths[len(self.spectrum.wavelengths)/2-100], 0.95), fontsize=25, type='lineedit')
    
    def add_fits_information_label(self):
        info_text = "Object Name: {}, type: {}, spectral class: {}\nCoordinates: {}\nDate: {}\nObserver: {}\nEquipment: {}\nPosition: {}".format(
                self.object_properties.name,
                self.object_properties.type,
                self.object_properties.sptype,
                self.object_properties.printable_coordinates(),
                self.object_properties.date.toString(),
                self.object_properties.observer,
                self.object_properties.equipment,
                self.object_properties.position
            )
        self.add_label(info_text, type='textbox', coords=(self.spectrum.wavelengths[len(self.spectrum.wavelengths)/4*3], 0.80), fontsize=14)
        self.profile_plot.figure.canvas.draw()
    
    def add_label(self, text=None, type='textbox', coords = None, fontsize = 12, color='black'):
        if not coords: coords = (self.spectrum.wavelengths[len(self.spectrum.wavelengths)/2], 0.5)
        self.labels.append((type, MoveableLabel(text=text if text else 'Label - double click to edit', on_dblclick=lambda l: self.edit_label(l, type=type), x=coords[0], y=coords[1], fontsize=fontsize, color=color, axes=self.profile_plot.axes)))
        self.profile_plot.figure.canvas.draw()
        
    def edit_label(self, label, type='lineedit'):
        def remove_label(self, label, dialog):
            label.remove()
            self.labels.remove([l for l in self.labels if l[1] == label][0])
            self.profile_plot.figure.canvas.draw()
            dialog.reject()
        dialog = QDialog()
        dialog.setWindowTitle("Edit Label")
        dialog.setLayout(QVBoxLayout())
        font_size = QSpinBox()
        font_size.setValue(label.get_fontsize())
        dialog.layout().addWidget(QLabel("Font Size"))
        dialog.layout().addWidget(font_size)
        text_edit = None
        if type == 'lineedit':
            text_edit = QLineEdit(label.get_text())
        else:
            text_edit = QTextEdit()
            text_edit.setPlainText(label.get_text())
            
        dialog.layout().addWidget(QLabel("Text"))
        dialog.layout().addWidget(text_edit)
        button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
        button_box.accepted.connect(dialog.accept)
        button_box.rejected.connect(dialog.reject)
        remove_button = QPushButton('Remove')
        remove_button.clicked.connect(lambda: remove_label(self, label, dialog))
        dialog.layout().addWidget(remove_button)
        dialog.layout().addWidget(button_box)
        if QDialog.Accepted != dialog.exec():
            return
        label.set_text(text_edit.text() if type=='lineedit' else text_edit.toPlainText())
        label.set_fontsize(font_size.value())
        label.axes.figure.canvas.draw()
Exemplo n.º 7
0
class VoxelData(object):

    # Constants for referring to axis
    X_AXIS = 1
    Y_AXIS = 2
    Z_AXIS = 3
    
    # World dimension properties
    @property
    def width(self):
        return self._width
    @property
    def height(self):
        return self._height
    @property
    def depth(self):
        return self._depth

    @property
    def changed(self):
        return self._changed
    @changed.setter
    def changed(self, value):
        if value and not self._changed:
            # Let whoever is watching us know about the change
            self._changed = value
            if self.notify_changed:
                self.notify_changed()
        self._changed = value

    @property
    def occlusion(self):
        return self._occlusion
    @occlusion.setter
    def occlusion(self, value):
        self._occlusion = value

    def __init__(self):
        # Default size
        self._width = _WORLD_WIDTH
        self._height = _WORLD_HEIGHT
        self._depth = _WORLD_DEPTH
        # Our undo buffer
        self._undo = Undo()
        # Init data
        self._initialise_data()
        # Callback when our data changes
        self.notify_changed = None
        # Ambient occlusion type effect
        self._occlusion = True

    # Initialise our data
    def _initialise_data(self):
        # Our scene data
        self._data = self.blank_data()
        # Our cache of non-empty voxels (coordinate groups)
        self._cache = []
        # Flag indicating if our data has changed
        self._changed = False
        # Reset undo buffer
        self._undo.clear()
        # Animation
        self._frame_count = 1
        self._current_frame = 0
        self._frames = [self._data]

    # Return an empty voxel space
    def blank_data(self):
        return [[[0 for _ in xrange(self.depth)]
            for _ in xrange(self.height)]
                for _ in xrange(self.width)]

    def is_valid_bounds(self, x, y, z):
        return (
            x >= 0 and x < self.width and
            y >= 0 and y < self.height and
            z >= 0 and z < self.depth
        )

    # Return the number of animation frames
    def get_frame_count(self):
        return self._frame_count

    # Change to the given frame
    def select_frame(self, frame_number):
        # Sanity
        if frame_number < 0 or frame_number >= self._frame_count:
            return
        # Make sure we really have a pointer to the current data
        self._frames[self._current_frame] = self._data
        # Change to new frame
        self._data = self._frames[frame_number]
        self._current_frame = frame_number
        self._undo.frame = self._current_frame
        self._cache_rebuild()
        self.changed = True

    # Add a new frame by copying the current one
    def add_frame(self, copy_current = True):
        if copy_current:
            data = self.get_data()
        else:
            data = self.blank_data()
        self._frames.insert(self._current_frame+1, data)
        self._undo.add_frame(self._current_frame+1)
        self._frame_count += 1
        self.select_frame(self._current_frame+1)

    # Delete the current frame
    def delete_frame(self):
        # Sanity - we can't have no frames at all
        if self._frame_count <= 1:
            return
        # Remember the frame we want to delete
        killframe = self._current_frame
        # Select a different frame
        self.select_previous_frame()
        # Remove the old frame
        del self._frames[killframe]
        self._undo.delete_frame(killframe)
        self._frame_count -= 1
        # If we only have one frame left, must be first frame
        if self._frame_count == 1:
            self._current_frame = 0
        # If we wrapped around, fix the frame pointer
        if self._current_frame > killframe:
            self._current_frame -= 1

    # Change to the next frame (with wrap)
    def select_next_frame(self):
        nextframe = self._current_frame+1
        if nextframe >= self._frame_count:
            nextframe = 0
        self.select_frame(nextframe)

    # Change to the previous frame (with wrap)
    def select_previous_frame(self):
        prevframe = self._current_frame-1
        if prevframe < 0:
            prevframe = self._frame_count-1
        self.select_frame(prevframe)

    # Get current frame number
    def get_frame_number(self):
        return self._current_frame

    # Set a voxel to the given state
    def set(self, x, y, z, state, undo = True):
        # If this looks like a QT Color instance, convert it
        if hasattr(state, "getRgb"):
            c = state.getRgb()
            state = c[0]<<24 | c[1]<<16 | c[2]<<8 | 0xff

        # Check bounds
        if ( not self.is_valid_bounds(x, y, z ) ):
            return False
        # Set the voxel
        if ( self.is_valid_bounds(x, y, z ) ):
            # Add to undo
            if undo:
                self._undo.add(UndoItem(Undo.SET_VOXEL, 
                (x, y, z, self._data[x][y][z]), (x, y, z, state)))
            self._data[x][y][z] = state
            if state != EMPTY:
                if (x,y,z) not in self._cache:
                    self._cache.append((x,y,z))
            else:
                if (x,y,z) in self._cache:
                    self._cache.remove((x,y,z))
        self.changed = True
        return True

    # Get the state of the given voxel
    def get(self, x, y, z):
        if ( not self.is_valid_bounds(x, y, z ) ):
            return EMPTY
        return self._data[x][y][z]

    # Return a copy of the voxel data
    def get_data(self):
        return copy.deepcopy(self._data)

    # Set all of our data at once
    def set_data(self, data):
        self._data = copy.deepcopy(data)
        self._cache_rebuild()
        self.changed = True

    # Clear our voxel data
    def clear(self):
        self._initialise_data()

    # Return full vertex list
    def get_vertices(self):
        vertices = []
        colours = []
        colour_ids = []
        normals = []
        uvs = []
        for x,y,z in self._cache:
            v, c, n, cid, uv = self._get_voxel_vertices(x, y, z)
            vertices += v
            colours += c
            normals += n
            colour_ids += cid
            uvs += uv
        return (vertices, colours, normals, colour_ids, uvs)

    # Called to notify us that our data has been saved. i.e. we can set
    # our "changed" status back to False.
    def saved(self):
        self.changed = False

    # Count the number of non-empty voxels from the list of coordinates
    def _count_voxels(self, coordinates):
        count = 0
        for x,y,z in coordinates:
            if self.get(x, y, z) != EMPTY:
                count += 1
        return count

    # Return the verticies for the given voxel. We center our vertices at the origin
    def _get_voxel_vertices(self, x, y, z):
        vertices = []
        colours = []
        normals = []
        colour_ids = []
        uvs = []

        # Remember voxel coordinates
        vx, vy, vz = x,y,z

        # Determine if we have filled voxels around us
        front = self.get(x, y, z-1) == EMPTY
        left = self.get(x-1, y, z) == EMPTY
        right = self.get(x+1, y, z) == EMPTY
        top = self.get(x, y+1, z) == EMPTY
        back = self.get(x, y, z+1) == EMPTY
        bottom = self.get(x, y-1, z) == EMPTY

        # Get our colour
        c = self.get(x, y, z)
        r = (c & 0xff000000)>>24
        g = (c & 0xff0000)>>16
        b = (c & 0xff00)>>8
        # Calculate shades for our 4 occlusion levels
        shades = []
        for c in range(5):
            shades.append((
                int(r*math.pow(OCCLUSION,c)),
                int(g*math.pow(OCCLUSION,c)),
                int(b*math.pow(OCCLUSION,c))))

        # Encode our voxel space coordinates as colours, used for face selection
        # We use 7 bits per coordinate and the bottom 3 bits for face:
        #   0 - front
        #   1 - top
        #   2 - left
        #   3 - right
        #   4 - back
        #   5 - bottom
        voxel_id = (x & 0x7f)<<17 | (y & 0x7f)<<10 | (z & 0x7f)<<3
        id_r = (voxel_id & 0xff0000)>>16
        id_g = (voxel_id & 0xff00)>>8
        id_b = (voxel_id & 0xff)

        # Adjust coordinates to the origin
        x, y, z = self.voxel_to_world(x, y, z)

        # Front face
        if front:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx,vy+1,vz-1) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx-1,vy,vz-1) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx+1,vy,vz-1) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx,vy-1,vz-1) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx-1,vy-1,vz-1) != EMPTY:
                    occ1 += 1
                if self.get(vx-1,vy+1,vz-1) != EMPTY:
                    occ2 += 1
                if self.get(vx+1,vy-1,vz-1) != EMPTY:
                    occ3 += 1
                if self.get(vx+1,vy+1,vz-1) != EMPTY:
                    occ4 += 1
            vertices += (x,   y,   z)
            colours += shades[occ1]
            vertices += (x,   y+1, z)
            colours += shades[occ2]
            vertices += (x+1, y,   z)
            colours += shades[occ3]
            vertices += (x+1, y,   z)
            colours += shades[occ3]
            vertices += (x,   y+1, z)
            colours += shades[occ2]
            vertices += (x+1, y+1, z)
            colours += shades[occ4]
            uvs += (0,0,0,1,1,0,1,0,0,1,1,1)
            normals += (0, 0, 1) * 6
            colour_ids += (id_r, id_g, id_b) * 6
        # Top face
        if top:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx,vy+1,vz+1) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx-1,vy+1,vz) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx+1,vy+1,vz) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx,vy+1,vz-1) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx-1,vy+1,vz-1) != EMPTY:
                    occ1 += 1
                if self.get(vx+1,vy+1,vz-1) != EMPTY:
                    occ3 += 1
                if self.get(vx+1,vy+1,vz+1) != EMPTY:
                    occ4 += 1
                if self.get(vx-1,vy+1,vz+1) != EMPTY:
                    occ2 += 1
            vertices += (x,   y+1, z)
            colours += shades[occ1]
            vertices += (x,   y+1, z-1)
            colours += shades[occ2]
            vertices += (x+1, y+1, z)
            colours += shades[occ3]
            vertices += (x+1, y+1, z)
            colours += shades[occ3]
            vertices += (x,   y+1, z-1)
            colours += shades[occ2]
            vertices += (x+1, y+1, z-1)
            colours += shades[occ4]
            uvs += (0,0,0,1,1,0,1,0,0,1,1,1)
            normals += (0, 1, 0) * 6
            colour_ids += (id_r, id_g, id_b | 1) * 6
        # Right face
        if right:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx+1,vy+1,vz) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx+1,vy,vz-1) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx+1,vy,vz+1) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx+1,vy-1,vz) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx+1,vy-1,vz-1) != EMPTY:
                    occ1 += 1
                if self.get(vx+1,vy+1,vz-1) != EMPTY:
                    occ2 += 1
                if self.get(vx+1,vy-1,vz+1) != EMPTY:
                    occ3 += 1
                if self.get(vx+1,vy+1,vz+1) != EMPTY:
                    occ4 += 1
            vertices += (x+1, y, z)
            colours += shades[occ1]
            vertices += (x+1, y+1, z)
            colours += shades[occ2]
            vertices += (x+1, y, z-1)
            colours += shades[occ3]
            vertices += (x+1, y, z-1)
            colours += shades[occ3]
            vertices += (x+1, y+1, z)
            colours += shades[occ2]
            vertices += (x+1, y+1, z-1)
            colours += shades[occ4]
            uvs += (0,0,0,1,1,0,1,0,0,1,1,1)
            normals += (1, 0, 0) * 6
            colour_ids += (id_r, id_g, id_b | 3) * 6
        # Left face
        if left:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx-1,vy+1,vz) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx-1,vy,vz+1) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx-1,vy,vz-1) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx-1,vy-1,vz) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx-1,vy-1,vz+1) != EMPTY:
                    occ1 += 1
                if self.get(vx-1,vy+1,vz+1) != EMPTY:
                    occ2 += 1
                if self.get(vx-1,vy-1,vz-1) != EMPTY:
                    occ3 += 1
                if self.get(vx-1,vy+1,vz-1) != EMPTY:
                    occ4 += 1
            vertices += (x, y, z-1)
            colours += shades[occ1]
            vertices += (x, y+1, z-1)
            colours += shades[occ2]
            vertices += (x, y, z)
            colours += shades[occ3]
            vertices += (x, y, z)
            colours += shades[occ3]
            vertices += (x, y+1, z-1)
            colours += shades[occ2]
            vertices += (x, y+1, z)
            colours += shades[occ4]
            uvs += (0,0,0,1,1,0,1,0,0,1,1,1)
            normals += (-1, 0, 0) * 6
            colour_ids += (id_r, id_g, id_b | 2) * 6
        # Back face
        if back:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx,vy+1,vz+1) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx+1,vy,vz+1) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx-1,vy,vz+1) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx,vy-1,vz+1) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx+1,vy-1,vz+1) != EMPTY:
                    occ1 += 1
                if self.get(vx+1,vy+1,vz+1) != EMPTY:
                    occ2 += 1
                if self.get(vx-1,vy-1,vz+1) != EMPTY:
                    occ3 += 1
                if self.get(vx-1,vy+1,vz+1) != EMPTY:
                    occ4 += 1
            vertices += (x+1, y, z-1)
            colours += shades[occ1]
            vertices += (x+1, y+1, z-1)
            colours += shades[occ2]
            vertices += (x, y, z-1)
            colours += shades[occ3]
            vertices += (x, y, z-1)
            colours += shades[occ3]
            vertices += (x+1, y+1, z-1)
            colours += shades[occ2]
            vertices += (x, y+1, z-1)
            colours += shades[occ4]
            uvs += (0,0,0,1,1,0,1,0,0,1,1,1)
            normals += (0, 0, -1) * 6
            colour_ids += (id_r, id_g, id_b | 4) * 6
        # Bottom face
        if bottom:
            occ1 = 0
            occ2 = 0
            occ3 = 0
            occ4 = 0
            if self._occlusion:
                if self.get(vx,vy-1,vz-1) != EMPTY:
                    occ2 += 1
                    occ4 += 1
                if self.get(vx-1,vy-1,vz) != EMPTY:
                    occ1 += 1
                    occ2 += 1
                if self.get(vx+1,vy-1,vz) != EMPTY:
                    occ3 += 1
                    occ4 += 1
                if self.get(vx,vy-1,vz+1) != EMPTY:
                    occ1 += 1
                    occ3 += 1
                if self.get(vx-1,vy-1,vz+1) != EMPTY:
                    occ1 += 1
                if self.get(vx-1,vy-1,vz-1) != EMPTY:
                    occ2 += 1
                if self.get(vx+1,vy-1,vz+1) != EMPTY:
                    occ3 += 1
                if self.get(vx+1,vy-1,vz-1) != EMPTY:
                    occ4 += 1
            vertices += (x, y, z-1)
            colours += shades[occ1]
            vertices += (x, y, z)
            colours += shades[occ2]
            vertices += (x+1, y, z-1)
            colours += shades[occ3]
            vertices += (x+1, y, z-1)
            colours += shades[occ3]
            vertices += (x, y, z)
            colours += shades[occ2]
            vertices += (x+1, y, z)
            colours += shades[occ4]
            uvs += (0,0,0,1,1,0,1,0,0,1,1,1)
            normals += (0, -1, 0) * 6
            colour_ids += (id_r, id_g, id_b | 5) * 6

        return (vertices, colours, normals, colour_ids, uvs)


    # Return vertices for a floor grid
    def get_grid_vertices(self):
        grid = []
        #builds the Y_plane
        for z in xrange(self.depth+1):
            gx, gy, gz = self.voxel_to_world(0, 0, z)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(self.width, 0, z)
            grid += (gx, gy, gz)
        for x in xrange(self.width+1):
            gx, gy, gz = self.voxel_to_world(x, 0, 0)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(x, 0, self.depth)
            grid += (gx, gy, gz)
        #builds the Z_plane
        for x in xrange(self.width+1):
            gx, gy, gz = self.voxel_to_world(x, 0, self.depth)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(x, self.height, self.depth)
            grid += (gx, gy, gz)
        for y in xrange(self.height+1):
            gx, gy, gz = self.voxel_to_world(0, y, self.depth)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(self.width, y, self.depth)
            grid += (gx, gy, gz)
        #builds the X_plane
        for y in xrange(self.height+1):
            gx, gy, gz = self.voxel_to_world(0, y, 0)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(0, y, self.depth)
            grid += (gx, gy, gz)
        for z in xrange(self.depth+1):
            gx, gy, gz = self.voxel_to_world(0, 0, z)
            grid += (gx, gy, gz)
            gx, gy, gz = self.voxel_to_world(0, self.height, z)
            grid += (gx, gy, gz)
        return grid

    # Convert voxel space coordinates to world space
    def voxel_to_world(self, x, y, z):
        x = (x - self.width//2)-0.5
        y = (y - self.height//2)-0.5
        z = (z - self.depth//2)-0.5
        z = -z
        return x, y, z

    # Convert world space coordinates to voxel space
    def world_to_voxel(self, x, y, z):
        x = (x + self.width//2)+0.5
        y = (y + self.height//2)+0.5
        z = (z - self.depth//2)-0.5
        z = -z
        return x, y, z

    # Rebuild our cache
    def _cache_rebuild(self):
        self._cache = []
        for x in range(self.width):
            for z in range(self.depth):
                for y in range(self.height):
                    if self._data[x][y][z] != EMPTY:
                        self._cache.append((x, y, z))

    # Calculate the actual bounding box of the model in voxel space
    # Consider all animation frames
    def get_bounding_box(self):
        minx = 999
        miny = 999
        minz = 999
        maxx = -999
        maxy = -999
        maxz = -999
        for data in self._frames:
            for x in range(self.width):
                for z in range(self.depth):
                    for y in range(self.height):
                        if data[x][y][z] != EMPTY:
                            if x < minx:
                                minx = x
                            if x > maxx:
                                maxx = x
                            if y < miny:
                                miny = y
                            if y > maxy:
                                maxy = y
                            if z < minz:
                                minz = z
                            if z > maxz:
                                maxz = z
        width = (maxx-minx)+1
        height = (maxy-miny)+1
        depth = (maxz-minz)+1
        return minx, miny, minz, width, height, depth

    # Resize the voxel space. If no dimensions given, adjust to bounding box.
    # We offset all voxels on all axis by the given amount.
    # Resize all animation frames
    def resize(self, width = None, height = None, depth = None, shift = 0):
        # Reset undo buffer
        self._undo.clear()
        # No dimensions, use bounding box
        mx, my, mz, cwidth, cheight, cdepth = self.get_bounding_box()
        if not width:
            width, height, depth = cwidth, cheight, cdepth
        for i, frame in enumerate(self._frames):
            # Create new data structure of the required size
            data = [[[0 for _ in xrange(depth)]
                for _ in xrange(height)]
                    for _ in xrange(width)]
            # Adjust ranges
            movewidth = min(width, cwidth)
            moveheight = min(height, cheight)
            movedepth = min(depth, cdepth)
            # Calculate translation
            dx = (0-mx)+shift
            dy = (0-my)+shift
            dz = (0-mz)+shift
            # Copy data over at new location
            for x in xrange(mx, mx+movewidth):
                for y in xrange(my, my+moveheight):
                    for z in xrange(mz, mz+movedepth):
                        data[x+dx][y+dy][z+dz] = frame[x][y][z]
            self._frames[i] = data
        self._data = self._frames[self._current_frame]
        # Set new dimensions
        self._width = width
        self._height = height
        self._depth = depth
        # Rebuild our cache
        self._cache_rebuild()
        self.changed = True

    # Rotate voxels in voxel space 90 degrees
    def rotate_about_axis(self, axis):
        # Reset undo buffer
        self._undo.clear()

        if axis == self.Y_AXIS:
            width = self.depth # note swap
            height = self.height
            depth = self.width
        elif axis == self.X_AXIS:
            width = self.width
            height = self.depth
            depth = self.height
        elif axis == self.Z_AXIS:
            width = self.height
            height = self.width
            depth = self.depth

        for i, frame in enumerate(self._frames):
                    
            # Create new temporary data structure
            data = [[[0 for _ in xrange(depth)]
                for _ in xrange(height)]
                    for _ in xrange(width)]
            
            # Copy data over at new location
            for tx in xrange(0, self.width):
                for ty in xrange(0, self.height):
                    for tz in xrange(0, self.depth):
                        if axis == self.Y_AXIS:
                            dx = (-tz)-1
                            dy = ty
                            dz = tx
                        elif axis == self.X_AXIS:
                            dx = tx
                            dy = (-tz)-1
                            dz = ty
                        elif axis == self.Z_AXIS:
                            dx = ty
                            dy = (-tx)-1
                            dz = tz
                        data[dx][dy][dz] = frame[tx][ty][tz]
            self._frames[i] = data
        
        self._width = width
        self._height = height
        self._depth = depth
        
        self._data = self._frames[self._current_frame]
        # Rebuild our cache
        self._cache_rebuild()
        self.changed = True
    
    # Translate the voxel data.
    def translate(self, x, y, z, undo = True):
        # Sanity
        if x == 0 and y == 0 and z == 0:
            return
        
        # Add to undo
        if undo:
            self._undo.add(UndoItem(Undo.TRANSLATE, 
            (-x, -y, -z), (x, y, z)))
        
        # Create new temporary data structure
        data = [[[0 for _ in xrange(self.depth)]
            for _ in xrange(self.height)]
                for _ in xrange(self.width)]
        # Copy data over at new location
        for tx in xrange(0, self.width):
            for ty in xrange(0, self.height):
                for tz in xrange(0, self.depth):
                    dx = (tx+x) % self.width
                    dy = (ty+y) % self.height
                    dz = (tz+z) % self.depth
                    data[dx][dy][dz] = self._data[tx][ty][tz]
        self._data = data
        self._frames[self._current_frame] = self._data
        # Rebuild our cache
        self._cache_rebuild()
        self.changed = True

    # Undo previous operation
    def undo(self):
        op = self._undo.undo()
        # Voxel edit
        if op and op.operation == Undo.SET_VOXEL:
            data = op.olddata
            self.set(data[0], data[1], data[2], data[3], False)
        # Translation
        elif op and op.operation == Undo.TRANSLATE:
            data = op.olddata
            self.translate(data[0], data[1], data[2], False)
            
    # Redo an undone operation
    def redo(self):
        op = self._undo.redo()
        # Voxel edit
        if op and op.operation == Undo.SET_VOXEL:
            data = op.newdata
            self.set(data[0], data[1], data[2], data[3], False)
        # Translation
        elif op and op.operation == Undo.TRANSLATE:
            data = op.newdata
            self.translate(data[0], data[1], data[2], False)

    # Enable/Disable undo buffer
    def disable_undo(self):
        self._undo.enabled = False
    def enable_undo(self):
        self._undo.enabled = True
Exemplo n.º 8
0
class PlotsMath(QWidget):
    
    F_X = Qt.UserRole + 1
    FITS_SPECTRUM = Qt.UserRole + 2
    
    def __init__(self, settings, database, project=None):
        super(PlotsMath, self).__init__()
        self.ui = Ui_PlotsMath()
        self.ui.setupUi(self)
        self.settings = settings
        self.project=project
        self.plot = QtCommons.nestWidget(self.ui.plot, QMathPlotWidget())
        self.reference_dialog = ReferenceSpectraDialog(database)
        self.reference_dialog.fits_picked.connect(self.open_fits)
        self.toolbar = QToolBar('Instrument Response Toolbar')
        open_btn = QtCommons.addToolbarPopup(self.toolbar, text="Open...", icon_file=':/new_open_20')
        open_file_action = open_btn.menu().addAction('FITS file')
        open_btn.menu().addAction('Reference library', self.reference_dialog.show)
        self.blackbody_menu = blackbody.BlackBodyAction(self.blackbody, open_btn.menu())
        
        if project:
            save_result = QtCommons.addToolbarPopup(self.toolbar, text='Save', icon_file=':/save_20')
            save_result.menu().addAction('As File', lambda: QtCommons.save_file('Save Operation Result...', FITS_EXTS, lambda f: self.save(f[0]), project.path))
            save_result.menu().addAction('As Instrument Response', self.save_project_instrument_response)
            open_file_action.triggered.connect(lambda: QtCommons.open_file('Open FITS Spectrum',FITS_EXTS, lambda f: self.open_fits(f[0]), project.path))
        else:
            open_file_action.triggered.connect(lambda: open_file_sticky('Open FITS Spectrum',FITS_EXTS, lambda f: self.open_fits(f[0]), self.settings, CALIBRATED_PROFILE, [RAW_PROFILE]))
            self.toolbar.addAction(QIcon(':/save_20'), 'Save', lambda: save_file_sticky('Save Operation Result...', 'FITS file (.fit)', lambda f: self.save(f[0]), self.settings, MATH_OPERATION, [CALIBRATED_PROFILE]))
            
        self.toolbar.addAction('Set operand', self.set_operand)
        self.toolbar.addSeparator()
        self.toolbar.addAction(self.ui.actionZoom)
        self.ui.actionZoom.triggered.connect(self.start_zoom)
        self.toolbar.addAction(self.ui.actionReset_Zoom)
        self.ui.actionReset_Zoom.triggered.connect(self.reset_zoom)
        self.toolbar.addSeparator()
        self.operands_model = QStandardItemModel()
        self.ui.operands_listview.setModel(self.operands_model)
        remove_btn = QtCommons.addToolbarPopup(self.toolbar, text='Remove...')
        remove_btn.menu().addAction(self.ui.actionSelectPointsToRemove)
        remove_btn.menu().addAction("Before point", lambda: spectrum_trim_dialog(self.spectrum, 'before', self.plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo))
        remove_btn.menu().addAction("After point", lambda: spectrum_trim_dialog(self.spectrum, 'after', self.plot.axes, lambda: self.draw(), self, before_removal=self.undo.save_undo))
        self.ui.clear_operands.clicked.connect(self.operands_model.clear)
        self.ui.remove_operand.clicked.connect(lambda: self.operands_model.removeRows(self.ui.operands_listview.selectionModel().selectedRows()[0].row(), 1))
            
        self.operands_model.rowsInserted.connect(lambda: self.ui.clear_operands.setEnabled(self.operands_model.rowCount() > 0) )
        self.operands_model.rowsRemoved.connect(lambda: self.ui.clear_operands.setEnabled(self.operands_model.rowCount() > 0) )
        self.ui.operands_listview.selectionModel().selectionChanged.connect(lambda s, u: self.ui.remove_operand.setEnabled(len(s)))
        self.ui.actionSelectPointsToRemove.triggered.connect(self.pick_rm_points)
        self.undo = Undo(None, self.draw)
        self.undo.add_actions(self.toolbar)
        self.ui.spline_factor.valueChanged.connect(self.factor_valueChanged)
        self.ui.spline_degrees.valueChanged.connect(lambda v: self.draw())
        self.ui.spline_factor_auto.toggled.connect(lambda v: self.draw())
        self.ui.spline_factor_auto.toggled.connect(lambda v: self.ui.spline_factor.setEnabled(not v))
        self.ui.execute.clicked.connect(self.execute_operation)
        self.plot.figure.tight_layout()
        
    def blackbody(self, blackbody):
        self.spectrum = blackbody.spectrum()
        self.spectrum_name = "Blackbody radiation for {0}".format(blackbody.kelvin)
        self.undo.set_spectrum(self.spectrum)
        self.spectrum.normalize_to_max()
        self.draw()
        

    def open_fits(self, filename):
        fits_file = fits.open(filename)
        fits_spectrum = FitsSpectrum(fits_file)
        self.spectrum_name = fits_spectrum.name()
        self.spectrum = fits_spectrum.spectrum
        self.undo.set_spectrum(self.spectrum)
        self.spectrum.normalize_to_max()
        if self.spectrum.dispersion() <0.4:
            print("dispersion too high ({}), reducing spectrum resolution".format(self.spectrum.dispersion()))
            self.spectrum.resample(self.spectrum.dispersion() / 0.4)
        self.draw()

    @pyqtSlot(float)
    def factor_valueChanged(self, f):
        self.draw()
        
    def pick_rm_points(self):
        self.plot.rm_element('zoom')
        self.plot.add_span_selector('pick_rm_points', lambda min,max: self.rm_points(min,max+1),direction='horizontal')
        
        
    def start_zoom(self):
        self.plot.rm_element('pick_rm_points')
        self.plot.select_zoom()
        
    def draw(self):
        self.ui.spline_degrees_value.setText("{}".format(self.ui.spline_degrees.value()))
        spline_factor = self.ui.spline_factor.value() if not self.ui.spline_factor_auto.isChecked() else None
        spline = UnivariateSpline(self.spectrum.wavelengths, self.spectrum.fluxes, k=self.ui.spline_degrees.value(), s=spline_factor)
        self.f_x = lambda x: spline(x)
        self.plot.plot(None, self.spectrum.wavelengths, self.spectrum.fluxes, '--', self.spectrum.wavelengths, spline(self.spectrum.wavelengths), '-')
        self.plot.figure.tight_layout()
        self.plot.figure.canvas.draw()
        
    def rm_points(self, wmin, wmax):
        self.undo.save_undo()
        x_min = self.spectrum.wavelength_index(max(self.spectrum.wavelengths[0], wmin))
        x_max = self.spectrum.wavelength_index(min(self.spectrum.wavelengths[-1], wmax))
        m=(self.spectrum.fluxes[x_max]-self.spectrum.fluxes[x_min])/(x_max-x_min)
        q = self.spectrum.fluxes[x_min]
        
        f = lambda x: x * m + q
        self.spectrum.fluxes[x_min:x_max] = np.fromfunction(f, self.spectrum.fluxes[x_min:x_max].shape)
        self.draw()
        
    def trim(self, direction):
        point = QInputDialog.getInt(None, 'Trim curve', 'Enter wavelength for trimming', self.spectrum.wavelengths[0] if direction == 'before' else self.spectrum.wavelengths[-1], self.spectrum.wavelengths[0], self.spectrum.wavelengths[-1])
        if not point[1]:
            return
        self.undo.save_undo()
        if direction == 'before':
            self.spectrum.cut(start=self.spectrum.wavelength_index(point[0]))
        else:
            self.spectrum.cut(end=self.spectrum.wavelength_index(point[0]))
        self.reset_zoom()
        self.draw()
    
    def set_operand(self):
        item = QStandardItem(self.spectrum_name)
        item.setData(self.f_x, PlotsMath.F_X)
        item.setData(self.spectrum, PlotsMath.FITS_SPECTRUM)
        self.operands_model.appendRow(item)
        
    def execute_operation(self):
        max_wavelengths = lambda operands: np.arange(max([o[0].wavelengths[0] for o in operands]), min([o[0].wavelengths[-1] for o in operands]))
        datasets = lambda operands, wavelengths: [PlotsMath.__data(o[1], wavelengths) for o in operands]
        operands = [(self.operands_model.item(a).data(PlotsMath.FITS_SPECTRUM), self.operands_model.item(a).data(PlotsMath.F_X)) for a in np.arange(self.operands_model.rowCount())]
        
        
        def divide(operands):
            if len(operands) > 2:
                print("Division supports only 2 operands, truncating")
            wavelengths = max_wavelengths(operands[0:2])
            datas = datasets(operands[0:2], wavelengths)
            return (wavelengths, datas[0]/datas[1])
        
        def mean(operands):
            wavelengths = max_wavelengths(operands)
            mean_data = np.zeros(wavelengths.shape)
            for data in datasets(operands, wavelengths):
                mean_data += data
            return (wavelengths, mean_data/len(wavelengths))
        
        operations = { 0: divide, 1: mean }
        try:
            wavelengths, data = operations[self.ui.operation_type.currentIndex()](operands)
            self.spectrum = Spectrum(data, wavelengths)
            
            self.spectrum.normalize_to_max()
            self.undo.set_spectrum(self.spectrum)
            self.ui.spline_degrees.setValue(5)
            self.ui.spline_factor.setValue(0)
            self.ui.spline_factor_auto.setChecked(False)
            self.draw()
        except IndexError:
            QMessageBox.warning(None, "Error", "Datasets are not compatible. Maybe you need to calibrate better, or use a different reference file")

    def save_project_instrument_response(self):
        name = QInputDialog.getText(self, 'Enter Name', 'Enter new instrument response name for saving')
        if name[1]:
            self.project.add_file(Project.INSTRUMENT_RESPONSES, lambda f: self.save(f), bare_name=name[0])

    def save(self, filename):
        hdu = fits.PrimaryHDU( PlotsMath.__data(self.f_x, self.spectrum.wavelengths))
        #hdu = fits.PrimaryHDU( self.spectrum.fluxes)
        fits_file = fits.HDUList([hdu])
        hdu.header['CRPIX1'] = 1
        hdu.header['CRVAL1'] = self.spectrum.wavelengths[0]
        hdu.header['CDELT1'] = self.spectrum.dispersion()
        hdu.writeto(filename, clobber=True)

    def reset_zoom(self):
        self.plot.reset_zoom(self.spectrum.wavelengths, self.spectrum.fluxes.min(), self.spectrum.fluxes.max())

    def __data(f_x, xrange):
        return np.fromfunction(lambda x: f_x(x+xrange[0]), xrange.shape)
Exemplo n.º 9
0
    def __init__(self, fits_file, settings, database, project=None):
        super(FinishSpectrum, self).__init__()
        self.settings = settings
        self.ui = Ui_FinishSpectrum()
        self.ui.setupUi(self)
        self.profile_line = None
        self.project = project
        self.fits_spectrum = FitsSpectrum(fits_file)
        self.undo = Undo(self.fits_spectrum.spectrum, self.draw)
        try:
            fits_file.index_of('ORIGINAL_DATA')
        except KeyError:
            hdu = fits.ImageHDU(data=fits_file[0].data,
                                header=fits_file[0].header,
                                name='ORIGINAL_DATA')
            fits_file.append(hdu)

        self.fits_spectrum.spectrum.normalize_to_max()
        self.spectrum = self.fits_spectrum.spectrum
        self.spectrum_plot = QtCommons.nestWidget(self.ui.plot,
                                                  QMathPlotWidget())
        self.spectrum_plot.mouse_moved.connect(
            Instances.MainWindow.print_coordinates)

        self.split_view()
        self.toolbar = QToolBar('Finish Spectrum Toolbar')
        if project:
            instrument_response_action = QtCommons.addToolbarPopup(
                self.toolbar, "Instrument Response")
            instrument_response_action.menu().addAction(
                'From FITS file...', lambda: open_file_sticky(
                    'Open Instrument Response Profile', FITS_EXTS, lambda f:
                    self.instrument_response(f[
                        0]), settings, MATH_OPERATION, [RAW_PROFILE]))
            for instrument_response in project.get_instrument_responses():
                print("Adding instrument response {}".format(
                    instrument_response))
                instrument_response_action.menu().addAction(
                    os.path.basename(instrument_response[1]),
                    lambda: self.instrument_response(instrument_response[1]))
        else:
            self.toolbar.addAction(
                'Instrument Response', lambda: open_file_sticky(
                    'Open Instrument Response Profile', FITS_EXTS, lambda f:
                    self.instrument_response(f[
                        0]), settings, MATH_OPERATION, [RAW_PROFILE]))
        self.toolbar.addAction(
            "Zoom",
            lambda: self.spectrum_plot.select_zoom(self.profile_plot.axes))
        self.toolbar.addAction(
            "Reset Zoom", lambda: self.spectrum_plot.reset_zoom(
                self.spectrum.wavelengths, self.spectrum.fluxes.min(),
                self.spectrum.fluxes.max(), self.profile_plot.axes))
        remove_action = QtCommons.addToolbarPopup(self.toolbar, "Remove")
        remove_action.menu().addAction(
            "Before point",
            lambda: spectrum_trim_dialog(self.spectrum,
                                         'before',
                                         self.profile_plot.axes,
                                         lambda: self.draw(),
                                         self,
                                         before_removal=self.undo.save_undo))
        remove_action.menu().addAction(
            "After point",
            lambda: spectrum_trim_dialog(self.spectrum,
                                         'after',
                                         self.profile_plot.axes,
                                         lambda: self.draw(),
                                         self,
                                         before_removal=self.undo.save_undo))
        self.undo.add_actions(self.toolbar)
        self.toolbar.addSeparator()

        self.reference_spectra_dialog = ReferenceSpectraDialog(
            database, self.fits_spectrum.spectrum)
        self.reference_spectra_dialog.setup_menu(self.toolbar,
                                                 self.profile_plot.axes,
                                                 settings)

        lines_menu = QtCommons.addToolbarPopup(self.toolbar,
                                               "Spectral Lines..")
        lines_menu.menu().addAction('Lines Database',
                                    lambda: self.lines_dialog.show())
        lines_menu.menu().addAction('Custom line', self.add_custom_line)
        labels_action = QtCommons.addToolbarPopup(self.toolbar, "Labels..")
        self.object_properties = ObjectProperties(fits_file, project=project)
        labels_action.menu().addAction('Title', self.add_title)
        if self.object_properties:
            labels_action.menu().addAction('Information from FITS file',
                                           self.add_fits_information_label)
        labels_action.menu().addAction('Custom', self.add_label)

        self.object_properties_dialog = ObjectPropertiesDialog(
            settings, self.object_properties)
        self.toolbar.addAction("Object properties",
                               self.object_properties_dialog.show)

        self.labels, self.lines = [], []
        for label in self.fits_spectrum.labels():
            self.add_label(text=label['text'],
                           coords=label['coords'],
                           type=label['type'],
                           fontsize=label['fontsize'])

        self.toolbar.addSeparator()
        if project:
            self.toolbar.addAction(
                QIcon(':/image_20'), "Export Image...",
                lambda: QtCommons.save_file(
                    'Export plot to image',
                    'PNG (*.png);;PDF (*.pdf);;PostScript (*.ps);;SVG (*.svg)',
                    lambda f: self.save_image(f[0]),
                    project.directory_path(Project.EXPORTED_IMAGES)))
            self.toolbar.addAction(QIcon(':/save_20'), 'Save',
                                   self.save_finished_in_project)
        else:
            self.toolbar.addAction(
                QIcon(':/image_20'), "Export Image...",
                lambda: save_file_sticky(
                    'Export plot to image',
                    'PNG (*.png);;PDF (*.pdf);;PostScript (*.ps);;SVG (*.svg)',
                    lambda f: self.save_image(f[0]), self.settings,
                    EXPORT_IMAGES, [CALIBRATED_PROFILE]))
            self.toolbar.addAction(
                QIcon(':/save_20'), 'Save', lambda: save_file_sticky(
                    'Save plot...', 'FITS file (.fit)', lambda f: self.__save(
                        f[0]), self.settings, CALIBRATED_PROFILE))

        self.lines_dialog = LinesDialog(database, settings, self.spectrum_plot,
                                        self.profile_plot.axes)
        self.lines_dialog.lines.connect(self.add_lines)
        for line in self.fits_spectrum.lines_labels():
            self.lines.append(
                ReferenceLine(line['text'],
                              line['wavelength'],
                              self.profile_plot.axes,
                              lambda line: self.lines.remove(line),
                              show_wavelength=line['display_wavelength'],
                              fontsize=line['fontsize'],
                              position=line['position']))
Exemplo n.º 10
0
class FinishSpectrum(QWidget):
    def __init__(self, fits_file, settings, database, project=None):
        super(FinishSpectrum, self).__init__()
        self.settings = settings
        self.ui = Ui_FinishSpectrum()
        self.ui.setupUi(self)
        self.profile_line = None
        self.project = project
        self.fits_spectrum = FitsSpectrum(fits_file)
        self.undo = Undo(self.fits_spectrum.spectrum, self.draw)
        try:
            fits_file.index_of('ORIGINAL_DATA')
        except KeyError:
            hdu = fits.ImageHDU(data=fits_file[0].data,
                                header=fits_file[0].header,
                                name='ORIGINAL_DATA')
            fits_file.append(hdu)

        self.fits_spectrum.spectrum.normalize_to_max()
        self.spectrum = self.fits_spectrum.spectrum
        self.spectrum_plot = QtCommons.nestWidget(self.ui.plot,
                                                  QMathPlotWidget())
        self.spectrum_plot.mouse_moved.connect(
            Instances.MainWindow.print_coordinates)

        self.split_view()
        self.toolbar = QToolBar('Finish Spectrum Toolbar')
        if project:
            instrument_response_action = QtCommons.addToolbarPopup(
                self.toolbar, "Instrument Response")
            instrument_response_action.menu().addAction(
                'From FITS file...', lambda: open_file_sticky(
                    'Open Instrument Response Profile', FITS_EXTS, lambda f:
                    self.instrument_response(f[
                        0]), settings, MATH_OPERATION, [RAW_PROFILE]))
            for instrument_response in project.get_instrument_responses():
                print("Adding instrument response {}".format(
                    instrument_response))
                instrument_response_action.menu().addAction(
                    os.path.basename(instrument_response[1]),
                    lambda: self.instrument_response(instrument_response[1]))
        else:
            self.toolbar.addAction(
                'Instrument Response', lambda: open_file_sticky(
                    'Open Instrument Response Profile', FITS_EXTS, lambda f:
                    self.instrument_response(f[
                        0]), settings, MATH_OPERATION, [RAW_PROFILE]))
        self.toolbar.addAction(
            "Zoom",
            lambda: self.spectrum_plot.select_zoom(self.profile_plot.axes))
        self.toolbar.addAction(
            "Reset Zoom", lambda: self.spectrum_plot.reset_zoom(
                self.spectrum.wavelengths, self.spectrum.fluxes.min(),
                self.spectrum.fluxes.max(), self.profile_plot.axes))
        remove_action = QtCommons.addToolbarPopup(self.toolbar, "Remove")
        remove_action.menu().addAction(
            "Before point",
            lambda: spectrum_trim_dialog(self.spectrum,
                                         'before',
                                         self.profile_plot.axes,
                                         lambda: self.draw(),
                                         self,
                                         before_removal=self.undo.save_undo))
        remove_action.menu().addAction(
            "After point",
            lambda: spectrum_trim_dialog(self.spectrum,
                                         'after',
                                         self.profile_plot.axes,
                                         lambda: self.draw(),
                                         self,
                                         before_removal=self.undo.save_undo))
        self.undo.add_actions(self.toolbar)
        self.toolbar.addSeparator()

        self.reference_spectra_dialog = ReferenceSpectraDialog(
            database, self.fits_spectrum.spectrum)
        self.reference_spectra_dialog.setup_menu(self.toolbar,
                                                 self.profile_plot.axes,
                                                 settings)

        lines_menu = QtCommons.addToolbarPopup(self.toolbar,
                                               "Spectral Lines..")
        lines_menu.menu().addAction('Lines Database',
                                    lambda: self.lines_dialog.show())
        lines_menu.menu().addAction('Custom line', self.add_custom_line)
        labels_action = QtCommons.addToolbarPopup(self.toolbar, "Labels..")
        self.object_properties = ObjectProperties(fits_file, project=project)
        labels_action.menu().addAction('Title', self.add_title)
        if self.object_properties:
            labels_action.menu().addAction('Information from FITS file',
                                           self.add_fits_information_label)
        labels_action.menu().addAction('Custom', self.add_label)

        self.object_properties_dialog = ObjectPropertiesDialog(
            settings, self.object_properties)
        self.toolbar.addAction("Object properties",
                               self.object_properties_dialog.show)

        self.labels, self.lines = [], []
        for label in self.fits_spectrum.labels():
            self.add_label(text=label['text'],
                           coords=label['coords'],
                           type=label['type'],
                           fontsize=label['fontsize'])

        self.toolbar.addSeparator()
        if project:
            self.toolbar.addAction(
                QIcon(':/image_20'), "Export Image...",
                lambda: QtCommons.save_file(
                    'Export plot to image',
                    'PNG (*.png);;PDF (*.pdf);;PostScript (*.ps);;SVG (*.svg)',
                    lambda f: self.save_image(f[0]),
                    project.directory_path(Project.EXPORTED_IMAGES)))
            self.toolbar.addAction(QIcon(':/save_20'), 'Save',
                                   self.save_finished_in_project)
        else:
            self.toolbar.addAction(
                QIcon(':/image_20'), "Export Image...",
                lambda: save_file_sticky(
                    'Export plot to image',
                    'PNG (*.png);;PDF (*.pdf);;PostScript (*.ps);;SVG (*.svg)',
                    lambda f: self.save_image(f[0]), self.settings,
                    EXPORT_IMAGES, [CALIBRATED_PROFILE]))
            self.toolbar.addAction(
                QIcon(':/save_20'), 'Save', lambda: save_file_sticky(
                    'Save plot...', 'FITS file (.fit)', lambda f: self.__save(
                        f[0]), self.settings, CALIBRATED_PROFILE))

        self.lines_dialog = LinesDialog(database, settings, self.spectrum_plot,
                                        self.profile_plot.axes)
        self.lines_dialog.lines.connect(self.add_lines)
        for line in self.fits_spectrum.lines_labels():
            self.lines.append(
                ReferenceLine(line['text'],
                              line['wavelength'],
                              self.profile_plot.axes,
                              lambda line: self.lines.remove(line),
                              show_wavelength=line['display_wavelength'],
                              fontsize=line['fontsize'],
                              position=line['position']))

    def add_custom_line(self):
        wl = QInputDialog.getDouble(
            self, "Custom Line", "Enter line wavelength in Å",
            self.fits_spectrum.spectrum.wavelengths[0],
            self.fits_spectrum.spectrum.wavelengths[0],
            self.fits_spectrum.spectrum.wavelengths[-1], 3)
        if not wl[1]: return
        self.add_lines([{'name': 'Custom Line', 'lambda': wl[0]}])

    def add_lines(self, lines):
        for line in lines:
            self.lines.append(
                ReferenceLine(line['name'], line['lambda'],
                              self.profile_plot.axes,
                              lambda line: self.lines.remove(line)))

    def synthetize_img(wavelengths, fluxes):
        f_fluxes = lambda f: math.pow(f, 3 / 5)
        colors = [
            wavelength_to_rgb(w / 10., f_fluxes(fluxes[i]))
            for i, w in enumerate(wavelengths)
        ]
        im_height = 150
        colors = np.array(colors * im_height).reshape(im_height, len(colors),
                                                      4)
        return colors, im_height

    def split_view(self):
        figure = self.spectrum_plot.figure
        figure.clear()
        self.gs = gridspec.GridSpec(40, 1)
        self.profile_plot = figure.add_subplot(self.gs[0:-6])
        self.synthetize = figure.add_subplot(self.gs[-3:-1],
                                             sharex=self.profile_plot)
        self.synthetize.yaxis.set_visible(False)
        self.synthetize.xaxis.set_visible(False)
        self.draw()

    def draw(self):
        #        self.profile_plot.clear()
        if self.profile_line:
            self.profile_line.remove()
        self.profile_line = self.profile_plot.plot(self.spectrum.wavelengths,
                                                   self.spectrum.fluxes,
                                                   color='blue')[0]

        self.synthetize.axes.set_facecolor('black')

        with ThreadPoolExecutor(max_workers=1) as executor:
            future = executor.submit(FinishSpectrum.synthetize_img,
                                     self.spectrum.wavelengths,
                                     self.spectrum.fluxes)
            future.add_done_callback(lambda f: self.synthetize.imshow(
                f.result()[0],
                extent=[
                    self.spectrum.wavelengths[0], self.spectrum.wavelengths[
                        -1], 0,
                    f.result()[1]
                ]))

        self.profile_plot.axes.set_xlabel('wavelength (Å)')
        self.profile_plot.axes.set_ylabel('relative flux')
        self.profile_plot.axes.xaxis.set_major_locator(
            MaxNLocator(16))  # TODO: settings for customization?
        self.profile_plot.axes.xaxis.set_minor_locator(MaxNLocator(200))
        self.spectrum_plot.figure.canvas.draw()
        self.gs.tight_layout(self.spectrum_plot.figure)

    def instrument_response(self, filename):
        print("Applying instrument response {}".format(filename))
        instrument_response_file = fits.open(filename)
        instrument_response = FitsSpectrum(instrument_response_file)
        response = instrument_response.spectrum
        response.normalize_to_max()

        range = (max(response.wavelengths[0], self.spectrum.wavelengths[0]),
                 min(response.wavelengths[-1], self.spectrum.wavelengths[-1]))
        self.spectrum.cut(self.spectrum.wavelength_index(range[0]),
                          self.spectrum.wavelength_index(range[1]))
        spline = InterpolatedUnivariateSpline(response.wavelengths,
                                              response.fluxes)

        response_data = [spline(x) for x in self.spectrum.wavelengths]
        self.spectrum.fluxes /= response_data
        self.spectrum.normalize_to_max()
        self.draw()

    def save_image(self, filename):
        Notification('Image {} saved in {}'.format(os.path.basename(filename),
                                                   os.path.dirname(filename)),
                     title='File Saved',
                     type='success',
                     timeout=5)
        self.spectrum_plot.figure.savefig(filename,
                                          bbox_inches='tight',
                                          dpi=300)

    def save_finished_in_project(self):
        self.project.add_file(Project.FINISHED_PROFILES, self.__save,
                              self.object_properties)

    def __save(self, filename):
        self.fits_spectrum.save(filename,
                                spectral_lines=self.lines,
                                labels=self.labels)

    def add_title(self):
        title = self.object_properties.name if self.object_properties else 'Title - double click to edit'
        self.add_label(
            text=title,
            coords=(
                self.spectrum.wavelengths[len(self.spectrum.wavelengths) / 2 -
                                          100], 0.95),
            fontsize=25,
            type='lineedit')

    def add_fits_information_label(self):
        info_text = "Object Name: {}, type: {}, spectral class: {}\nCoordinates: {}\nDate: {}\nObserver: {}\nEquipment: {}\nPosition: {}".format(
            self.object_properties.name, self.object_properties.type,
            self.object_properties.sptype,
            self.object_properties.printable_coordinates(),
            self.object_properties.date.toString(),
            self.object_properties.observer, self.object_properties.equipment,
            self.object_properties.position)
        self.add_label(
            info_text,
            type='textbox',
            coords=(self.spectrum.wavelengths[len(self.spectrum.wavelengths) /
                                              4 * 3], 0.80),
            fontsize=14)
        self.profile_plot.figure.canvas.draw()

    def add_label(self,
                  text=None,
                  type='textbox',
                  coords=None,
                  fontsize=12,
                  color='black'):
        if not coords:
            coords = (
                self.spectrum.wavelengths[len(self.spectrum.wavelengths) / 2],
                0.5)
        self.labels.append(
            (type,
             MoveableLabel(
                 text=text if text else 'Label - double click to edit',
                 on_dblclick=lambda l: self.edit_label(l, type=type),
                 x=coords[0],
                 y=coords[1],
                 fontsize=fontsize,
                 color=color,
                 axes=self.profile_plot.axes)))
        self.profile_plot.figure.canvas.draw()

    def edit_label(self, label, type='lineedit'):
        def remove_label(self, label, dialog):
            label.remove()
            self.labels.remove([l for l in self.labels if l[1] == label][0])
            self.profile_plot.figure.canvas.draw()
            dialog.reject()

        dialog = QDialog()
        dialog.setWindowTitle("Edit Label")
        dialog.setLayout(QVBoxLayout())
        font_size = QSpinBox()
        font_size.setValue(label.get_fontsize())
        dialog.layout().addWidget(QLabel("Font Size"))
        dialog.layout().addWidget(font_size)
        text_edit = None
        if type == 'lineedit':
            text_edit = QLineEdit(label.get_text())
        else:
            text_edit = QTextEdit()
            text_edit.setPlainText(label.get_text())

        dialog.layout().addWidget(QLabel("Text"))
        dialog.layout().addWidget(text_edit)
        button_box = QDialogButtonBox(QDialogButtonBox.Ok
                                      | QDialogButtonBox.Cancel)
        button_box.accepted.connect(dialog.accept)
        button_box.rejected.connect(dialog.reject)
        remove_button = QPushButton('Remove')
        remove_button.clicked.connect(
            lambda: remove_label(self, label, dialog))
        dialog.layout().addWidget(remove_button)
        dialog.layout().addWidget(button_box)
        if QDialog.Accepted != dialog.exec():
            return
        label.set_text(text_edit.text() if type ==
                       'lineedit' else text_edit.toPlainText())
        label.set_fontsize(font_size.value())
        label.axes.figure.canvas.draw()