Qt新手在这里。我正在使用PyQt在QGraphicsView中创建可调整大小的QGraphicsScene。这与我提供的代码一起正常工作。我的解决方案的问题在于,当我使用resize函数以编程方式更新QMainWindow的大小时,图形场景的元素无法缩放。用鼠标手动调整大小确实会调用正确的行为。在ForestViewer类的resize调用中,使用提供的代码可以看到不正确的行为。通过手动调整窗口大小可以看到正确的行为。尽管所有UI工作都是在forestviewer.py中完成的,但该项目要构建四个模块。任何帮助将不胜感激!
OS- Windows ,Python版本- 2.7 ,PyQt版本- 5.9.2
forestviewer.py
from forest import Forest
from PyQt5.QtCore import QRectF, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QCheckBox, QComboBox, QDateTimeEdit,
QDial, QDialog, QGridLayout, QGroupBox, QHBoxLayout, QLabel, QLineEdit,
QProgressBar, QPushButton, QRadioButton, QScrollBar, QSizePolicy,
QSlider, QSpinBox, QStyleFactory, QTableWidget, QTabWidget, QTextEdit,
QVBoxLayout, QWidget, QGraphicsScene, QGraphicsView, QMainWindow,
QFormLayout)
from PyQt5.QtGui import QPen, QBrush, QColor
import config
class ForestViewer(QMainWindow):
resized = pyqtSignal()
def __init__(self, forest, parent=None):
super(ForestViewer, self).__init__(parent=parent)
self.forest = forest
self.ForestDialog = ForestDialog( self.forest )
self.resized.connect( self.handleResize )
self.setCentralWidget( self.ForestDialog)
self.setWindowTitle("Chestnut Blight Forest Simulator")
QApplication.setStyle(QStyleFactory.create('Fusion'))
self.resize(1200,900) #<-- Does not resize grid
self.ForestDialog.repaint()
def resizeEvent(self, event):
self.resized.emit()
return super(ForestViewer, self).resizeEvent(event)
def handleResize(self):
self.ForestDialog.repaint()
class ForestDialog(QDialog):
def __init__(self, forest=None, parent=None):
super(ForestDialog, self).__init__(parent)
self.forest = forest
self.setContentsMargins(1,1,1,1)
self.createForestView()
self.createForestControl()
self.mainLayout = QGridLayout()
self.mainLayout.addWidget(self.forest_view_box, 0, 0)
self.mainLayout.addWidget(self.ForestControlBox, 0, 1)
self.setLayout(self.mainLayout)
def createForestView(self):
self.forest_view_box = QGroupBox("Forest View")
forest_scene = QGraphicsScene() #init size?
forest_scene.setBackgroundBrush(QColor("orange"))
self.forest_view = QGraphicsView(forest_scene)
self.forest_view_box.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Minimum)
layout = QHBoxLayout(self)
layout.addWidget(self.forest_view)
self.forest_view_box.setLayout(layout)
if self.forest != None:
self.paintGrid()
def createForestControl(self):
self.ForestControlBox = QGroupBox("Forest Control")
years_to_sim = QSpinBox(self.ForestControlBox)
years_to_sim.setValue(50)
update_delay = QSpinBox(self.ForestControlBox)
update_delay.setValue(0)
layout = QFormLayout()
layout.addRow(QLabel("Forest Size: "))
layout.addRow(QLabel("Years to Simulate:"), years_to_sim)
layout.addRow(QLabel("Update Delay (seconds):"), update_delay)
self.ForestControlBox.setLayout(layout)
def paintGrid(self):
forest = self.forest
if forest == None:
print("No Forest")
return
view = self.forest_view # group = self.forest_view_box
scene = view.scene()
scene.setBackgroundBrush(QColor("red"))
rect = scene.itemsBoundingRect()
contents = view.contentsRect()
w = float(contents.width())
h = float(contents.height()) #view.childrenRect()
scene.clear()
view.setSceneRect(0, 0, w, h)
cell_w = w / forest.cols
cell_h = h / forest.rows
rad = min(cell_w, cell_h)
grid = forest.grid
for r in range(forest.rows):
for c in range(forest.cols):
x = c * cell_w
y = r * cell_h
rect = QRectF(x, y, cell_w, cell_h)
bg_col = QColor("#EDEDED")
off_white = QColor("#E8E8E8")
trans = QColor("transparent")
scene.addRect(rect, QPen(bg_col), QBrush(off_white))
tree = grid[r][c]
if tree != None and tree.stage != config.DEAD:
qcol = self.colorFromTree(tree)
mrad = tree.stage * 0.24 * rad # define circle radius based on tree size
xe = x + ((cell_w - mrad) / 2)
ye = y + ((cell_h - mrad) / 2)
scene.addEllipse(xe, ye, mrad, mrad, QPen(trans), QBrush(qcol))
def colorFromTree(self, tree):
col = "transparent"
if tree != None:
col_switch = {
config.V: "red",
config.HV: "turquoise",
config.HEALTHY: "green"
}
col = col_switch.get(tree.rating)
return QColor(col)
def repaint(self):
if(self.forest_view != None and self.forest != None):
self.paintGrid()
forest.py
from tree import Tree
import random
import math
import config
from copy import copy, deepcopy
class Forest:
num_deaths, num_births = 0, 0
# TODO: Add getters, setters?
def __init__(self, rows, cols):
self.rows = rows
self.cols = cols
self.grid = [[None] * cols for i in range(rows)]
# Generates a random Tree grid based on 2002 CDF data
def generate_grid(self):
new_grid = [[None] * self.cols for i in range(self.rows)]
for row in range(self.rows):
for col in range(self.cols):
rating = 0 # TODO enum?
stage = 0
rand = random.random()
if rand < config.TREE_DENSITY:
tree_type = random.random()
i = 0
while i <= len(config.POP_2002_CDF):
if tree_type < config.POP_2002_CDF[i]:
rating = i / config.DBH_STAGE4 + 1
stage = i % config.DBH_STAGE4 + 1
break
i += 1
new_tree = Tree(row, col, rating, stage, config.UNTREATED)
new_grid[row][col] = new_tree
return new_grid
def print_forest(self):
grid = self.grid
for row in grid:
print(' '.join([str(tree.stage) for tree in row]))
# above only prints stage, uncomment below for full tree details
# for tree in row
#tree.print_tree()
def init_random(self):
self.grid = self.generate_grid()
# Returns a 2D list of lists of trees, i.e., the new grid
# TODO: Add infect function
def get_next_year(self):
prev_year = self.grid
next_year = deepcopy(self.grid)
coords = [(r,c) for r in range(self.rows) for c in range(self.cols)]
random.shuffle(coords)
for coord in coords:
r = coord[0]
c = coord[1]
tree = prev_year[coord[0]][coord[1]] # original tree
t_tree = next_year[coord[0]][coord[1]] # transformed tree
if tree.stage != config.DEAD:
rand = random.random()
next_stage_row = ((tree.rating - 1) * config.DBH_STAGE4) + \
(tree.stage - 1)
i = 0
while rand >= config.NEW_STAGE_CDF[next_stage_row][i] and \
i < config.DBH_STAGE4 + 1:
next_year[r][c].stage = i
i += 1
# block not required?
if tree.stage == config.DEAD:
t_tree.stage = config.DEAD
t_tree.treatment = config.UNTREATED
# TODO: decide if reset tree treatment here
else:
rand = random.random()
next_rating_row = tree.treatment * (config.HEALTHY - 1) \
+ (tree.rating - 1)
i = 0
while i < config.HEALTHY - 1 and rand >= \
config.NEW_RATING_CDF[next_rating_row][i]:
next_year[r][c].rating = i + 1
i += 1
#if tree.rating == config.V:
#infect(config.V, r, c, prev_year, next_year)
#else if tree.rating == config.HV
#infect(config.HV, ...)
rep = config.REPRODUCTION[tree.rating - 1][tree.stage - 1]
l = math.exp(-rep)
p = random.random()
rand_poisson = 1
while p > l :
p = p * random.random()
rand_poisson += 1
rand_poisson -= 1
sites = copy(coords)
random.shuffle(sites)
while rand_poisson > 0 and len(sites) > 0:
site = sites.pop()
s_r = site[0]
s_c = site[1]
if prev_year[s_r][s_c].stage == config.DEAD:
print("add new tree")
next_year[s_r][s_c].stage = config.DBH_STAGE1
next_year[s_r][s_c].rating = config.HEALTHY
self.num_births += 1
rand_poisson -= 1
return next_year
# Generates a new grid for the Forest and sets the active grid to
# the grid of the next year
def set_next_year(self):
next_year = self.get_next_year()
self.grid = next_year
tree.py
class Tree:
def __init__(self, r = 0, c = 0, rating = 0, stage = 0, treatment = 0):
self._r = r # row
self._c = c
self._rating = rating # health
self._stage = stage # size
self._treatment = treatment # treatment
#def __eq__(self, other):
# May need to define custom '==' operator or .equals function
def print_tree(self):
print( "Tree (" + str(self.x) + ", " + str(self.y) + "):\n Rating: " +
str(self.rating) + "\n Stage: " + str(self.stage) +
"\n Treatment: " + str(self.treatment) )
# r: row of tree
@property
def r(self):
return self._r
@r.setter
def r(self, val):
self._r = val
# c: column of tree
@property
def c(self):
return self._c
@c.setter
def c(self, val):
self._c = val
# rating: health of tree ( V, HV, Healthy )
@property
def rating(self):
return self._rating
@rating.setter
def rating(self, val):
self._rating = val
# rating: health of tree ( V, HV, Healthy )
@property
def stage(self):
return self._stage
# stage: Size of tree, 0 = dead, 1-4 see DBH_STAGEs
@stage.setter
def stage(self, val):
self._stage = val
# treatment: 0 = untreated, 1 = treated
@property
def treatment(self):
return self._treatment
@treatment.setter
def treatment(self, val):
self._treatment = val
config.py
import math
"""
-------------------------------------------------------
SIMULATION SETTINGS
-------------------------------------------------------
"""
# Number of years for simulating, classifier training
YEARS = 200
# Size of a square within the world (2x2 meters)
SITE_SIZE = 2
# Scalar value used to determine infection range distance of a tree (meters)
DIST_CLASS = 8
# Used to calculate the number of infection "attempts" when determining which
# trees will get infected. This new value was introduced when modifying the
# infection algorithm to change some unintended behavior. The old method would
# calculate the number of trees to be infected within a certain distance and then
# infect them at random. This method did not take tree spacial locality into
# consideration when selecting a new tree to infect. Instead, the infected tree
# now selectes tiles at random, favoring those tiles closer to the source tree,
# given a number of sporing events. The current infection still mimics the
# anticipated West Salem data in the # of infections, but now takes more
# consideration to tree locality. The downside to this is that the simulation
# time increases.
SPORE_SCALAR = 400
# Forest height (SITESIZE tiles)
HEIGHT = 50
# Forest width (SITESIZE tiles)
WIDTH = 50
"""
-------------------------------------------------------
TREE STATE DEFINITIONS
-------------------------------------------------------
"""
# Titled a Simulation "Action", but really is a treatment STATE
UNTREATED = 0
# Titled a Simulation "Action", but really is a treatment STATE
TREATED = 1
# Virulent - A tree health rating. All virulent cankers
V = 1
# Hypovirulent - A tree health rating. Mix of virulent and hypovirulent cankers
HV = 2
# Healthy - A tree health rating.
HEALTHY = 3
# DBH Stage - Indicates a dead tree. (Diameter at Breast Height)
DEAD = 0
# DBH Stage - DBH <= 1 cm
DBH_STAGE1 = 1
# DBH Stage - 1 < DBH <= 10 cm
DBH_STAGE2 = 2
# DBH Stage - 10 < DBH <= 20 cm
DBH_STAGE3 = 3
# DBH Stage - DBH > 20 cm
DBH_STAGE4 = 4
"""
-------------------------------------------------------
GROWTH / INFECTION MATH
-------------------------------------------------------
"""
# From rough density analysis on West Salem plot, likelihood of a tree being
# in a site
TREE_DENSITY = 0.01071632 * SITE_SIZE * SITE_SIZE
# CDF of percentages from tree ratings in 2002
# (original values {0.2356828, 0.2048458, 0.5594714})
BEGIN_RATING = [0.2356828, 0.4405286, 1]
# CDF of percentages from tree stages in 2002
# (orig. values {0.1059603, 0.4282561, 0.2030905, 0.2626932})
BEGIN_STAGE = [0.1059603, 0.5342164, 0.7373069, 1]
# Used to determine tree reproduction, i.e. the creation of Stage 1 trees
REPRODUCTION = [
[0, 0.02, 0.32, 3.69],
[0, 0.016, 0.78, 2.43],
[0, 0.001, 0.03, 7.74]
]
# Probability of a virulent tree sporing event
PROB_OF_SPORE_VIRU = 1 - math.exp(-1.5)
# Probability of a hypovirulent tree sporing event
PROB_OF_SPORE_HYPO = 1 - math.exp(-.75)
# Previously used to calculate the number of infections.
# Usage:
# maxInfections = (int) math.round(math.exp(config.NUM_INF_CDF[0]*math.random()- config.NUM_INF_CDF[1]))
# Now is used to calculate the MAX number of infections.
NUM_INF_CDF = [5.6117, 3.6341]
# Percentage of infections from HV trees that result in HV infections
# (otherwise virulent infection)
PER_HV_TO_HV = 0.65
# NEW Distance Coefficients for Infect (4/22/18).
# Each instance represents a probability for an infection on 8m intervals
# e.g. 0.3915 probability spread 8m, 0.5195 probaility 16m, ...
V_INFECT_RANGE_PROB_8M_INT = [
0.3915, 0.5195, 0.6081, 0.6706, 0.7188, 0.7580,
0.7912, 0.8199, 0.8451, 0.8677, 0.8882, 0.9068,
0.9239, 0.9398, 0.9545, 0.9683, 0.9813, 0.9935,
1.0000
]
# NEW Distance Coefficients for Infect (4/22/18).
# Each instance represents a probability for an infection on 8m intervals
HV_INFECT_RANGE_PROB_8M_INT = [
0.5746, 0.6587, 0.7169, 0.7578, 0.7894, 0.8152, 0.8370, 0.8558, 0.8724,
0.8872, 0.9006, 0.9129, 0.9241, 0.9345, 0.9442, 0.9533, 0.9618, 0.9698,
0.9774, 0.9846, 0.9914, 0.9979, 1.0000
]
# Population dynamics of West Salem in 2002. Rows are virulent, hypovirulent
# and healthy. Each entry corresponds to a stage (size).
POP_2002_CDF = [
0.03311258, 0.09050773, 0.15011038, 0.23399558, 0.23399558, 0.27373068,
0.31567329, 0.43929360, 0.51214128, 0.84326711, 0.94481236, 1.00000000
]
# Used to calculate new ratings of trees in getNextYear(). Implementation is
# not exactly clear as to what is happening.
NEW_RATING_CDF = [
[0.69714286, 1],
[0.20588235, 1],
[0.52651515, 1],
[0.09884467, 1]
]
# Used to calculate new stages of trees in getNextYear().
# Implementation is not exactly clear as to what is happening.
NEW_STAGE_CDF = [
[0.2, 0.962, 0.999, 0.999, 1],
[0.006, 0.09, 0.99, 1, 1],
[0.05, 0.05,0.3, 0.96,1],
[0.021, 0.021, 0.101, 0.111, 1],
[0.18, 0.996, 1, 1, 1],
[0, 0.09, 0.98, 1, 1],
[0.01, 0.01, 0.05, 0.96, 1],
[0.001, 0.001, 0.006, 0.056, 1],
[0.16, 0.994, 1, 1, 1],
[0, 0.07, 0.99, 1, 1],
[0, 0, 0, 0.95, 1],
[0.013, 0.013, 0.013, 0.013, 1]
]
"""
-------------------------------------------------------
NEURAL NETWORK SETTINGS
-------------------------------------------------------
"""
# # of input nodes for neural network classifier
# (rating, stage, virulent neighbors, hypo-virulent neighbors, healthy neighbors)
INPUT_LAYER_NODE_COUNT = 5
# # of hidden nodes for neural network classifier
HIDDEN_LAYER_NODE_COUNT = 10
# # of output nodes for neural network classifier (good, bad)
OUTPUT_LAYER_NODE_COUNT = 2
# Defines the square range (NEIGHBOR_RADIUSxNEIGHBOR_RADIUS) of SITESIZE tiles
# to look at when determining the neighbors of a tree. The neighbors are used
# for Q-Learning. A higher neighbor_radius means a larger state space.
NEIGHBOR_RADIUS = 4