diff --git a/.gitignore b/.gitignore index 15fb17b133b4c707651b8411b4934d2734968688..a003eda10b9827f972a29ecccbe9b67c3cfa7de1 100644 --- a/.gitignore +++ b/.gitignore @@ -109,3 +109,4 @@ ENV/ images/test/ +test_save.dat diff --git a/MANIFEST.in b/MANIFEST.in index 965b2dda7db7c49f68857dc3aea9af37e30a745e..30bf97cf0de32bad7e630a80e2b977eb85d48911 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -3,6 +3,7 @@ include CONTRIBUTING.rst include HISTORY.rst include LICENSE include README.rst +include requirements_dev.txt recursive-include tests * recursive-exclude * __pycache__ diff --git a/examples/qt2.py b/examples/qt2.py new file mode 100644 index 0000000000000000000000000000000000000000..6074106523c7fbe503f79b6bc7604f055dc27b35 --- /dev/null +++ b/examples/qt2.py @@ -0,0 +1,78 @@ + + +import sys +from PyQt5 import QtSvg +from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QGridLayout, QWidget +from PyQt5.QtCore import Qt, QByteArray + +from flatland.utils import svg + + +# Subclass QMainWindow to customise your application's main window +class MainWindow(QMainWindow): + + def __init__(self, *args, **kwargs): + super(MainWindow, self).__init__(*args, **kwargs) + + self.setWindowTitle("My Awesome App") + + layout = QGridLayout() + layout.setSpacing(0) + + wMain = QWidget(self) + + wMain.setLayout(layout) + + label = QLabel("This is a PyQt5 window!") + + # The `Qt` namespace has a lot of attributes to customise + # widgets. See: http://doc.qt.io/qt-5/qt.html + label.setAlignment(Qt.AlignCenter) + layout.addWidget(label, 0, 0) + + svgWidget = QtSvg.QSvgWidget("./svg/Gleis_vertikal.svg") + layout.addWidget(svgWidget, 1, 0) + + if True: + track = svg.Track() + + svgWidget = None + iRow = 0 + iCol = 2 + iArt = 0 + nCols = 3 + for binTrans in list(track.dSvg.keys())[:2]: + sSVG = track.dSvg[binTrans].to_string() + + bySVG = bytearray(sSVG, encoding='utf-8') + + # with open(sfPath, "r") as fIn: + # sSVG = fIn.read() + # bySVG = bytearray(sSVG, encoding='utf-8') + + svgWidget = QtSvg.QSvgWidget() + oQB = QByteArray(bySVG) + + bSuccess = svgWidget.renderer().load(oQB) + # print(x0, y0, x1, y1) + print(iRow, iCol, bSuccess) + print("\n\n\n", bySVG.decode("utf-8")) + # svgWidget.setGeometry(x0, y0, x1, y1) + layout.addWidget(svgWidget, iRow, iCol) + + iArt += 1 + iRow = int(iArt / nCols) + iCol = iArt % nCols + + # Set the central widget of the Window. Widget will expand + # to take up all the space in the window by default. + self.setCentralWidget(wMain) + + +app = QApplication(sys.argv) + +window = MainWindow() +window.show() + +app.exec_() + diff --git a/examples/training_navigation.py b/examples/training_navigation.py index 23970a9059f15ce89b59b1cbcb9b862d248d1cd5..cabb655e3eb2bc2d12d908559b0102b072163052 100644 --- a/examples/training_navigation.py +++ b/examples/training_navigation.py @@ -5,7 +5,7 @@ from flatland.utils.rendertools import * from flatland.baselines.dueling_double_dqn import Agent from collections import deque import torch, random - +import time random.seed(1) np.random.seed(1) @@ -25,15 +25,16 @@ transition_probability = [15, # empty cell - Case 0 # Example generate a random rail """ -env = RailEnv(width=10, - height=10, +env = RailEnv(width=20, + height=20, rail_generator=random_rail_generator(cell_type_relative_proportion=transition_probability), number_of_agents=1) """ env = RailEnv(width=15, height=15, - rail_generator=complex_rail_generator(nr_start_goal=10, min_dist=5, max_dist=99999, seed=0), + rail_generator=complex_rail_generator(nr_start_goal=2, nr_extra=30, min_dist=5, max_dist=99999, seed=0), number_of_agents=3) + """ env = RailEnv(width=20, height=20, @@ -117,10 +118,13 @@ for trials in range(1, n_trials + 1): # Reset environment obs = env.reset() + final_obs = obs.copy() final_obs_next = obs.copy() + for a in range(env.get_num_agents()): data, distance = env.obs_builder.split_tree(tree=np.array(obs[a]), num_features_per_node=5, current_depth=0) + data = norm_obs_clip(data) distance = norm_obs_clip(distance) obs[a] = np.concatenate((data, distance)) @@ -136,7 +140,8 @@ for trials in range(1, n_trials + 1): # Run episode for step in range(100): if demo: - env_renderer.renderEnv(show=True) + env_renderer.renderEnv(show=True, obsrender=True) + time.sleep(2) # print(step) # Action for a in range(env.get_num_agents()): @@ -149,7 +154,6 @@ for trials in range(1, n_trials + 1): # Environment step next_obs, all_rewards, done, _ = env.step(action_dict) - for a in range(env.get_num_agents()): data, distance = env.obs_builder.split_tree(tree=np.array(next_obs[a]), num_features_per_node=5, current_depth=0) diff --git a/flatland/envs/agent_utils.py b/flatland/envs/agent_utils.py index 1f1bc1db065057a5ced1c917c5a24e2decebf2fa..05f81e43be3f33bfdfc81911d6cf6272bfba2d7e 100644 --- a/flatland/envs/agent_utils.py +++ b/flatland/envs/agent_utils.py @@ -1,6 +1,7 @@ from attr import attrs, attrib from itertools import starmap +import numpy as np # from flatland.envs.rail_env import RailEnv @@ -36,7 +37,18 @@ class EnvAgentStatic(object): return list(starmap(EnvAgentStatic, zip(positions, directions, targets))) def to_list(self): - return [self.position, self.direction, self.target] + + # I can't find an expression which works on both tuples, lists and ndarrays + # which converts them all to a list of native python ints. + lPos = self.position + if type(lPos) is np.ndarray: + lPos = lPos.tolist() + + lTarget = self.target + if type(lTarget) is np.ndarray: + lTarget = lTarget.tolist() + + return [lPos, int(self.direction), lTarget] @attrs diff --git a/flatland/envs/generators.py b/flatland/envs/generators.py index 04e9a8fefac4ada79527f00200dc6fbfa3d7b924..c1578a816e2e30127fb77dda6e72ab51b2f41cb2 100644 --- a/flatland/envs/generators.py +++ b/flatland/envs/generators.py @@ -9,7 +9,7 @@ from flatland.envs.env_utils import distance_on_rail, connect_rail, get_directio from flatland.envs.env_utils import get_rnd_agents_pos_tgt_dir_on_rail -def complex_rail_generator(nr_start_goal=1, nr_extra=10, min_dist=2, max_dist=99999, seed=0): +def complex_rail_generator(nr_start_goal=1, nr_extra=100, min_dist=20, max_dist=99999, seed=0): """ Parameters ------- diff --git a/flatland/envs/observations.py b/flatland/envs/observations.py index 8e4be0ba49d8405aa420d2bc8e4854f6300e3837..651d83520ee1f492ea71ba6ddf82cfa5f9093964 100644 --- a/flatland/envs/observations.py +++ b/flatland/envs/observations.py @@ -205,7 +205,7 @@ class TreeObsForRailEnv(ObservationBuilder): # observation = [0, 0, 0, 0, self.distance_map[handle, position[0], position[1], orientation]] observation = [0, 0, 0, 0, self.distance_map[(handle, *agent.position, agent.direction)]] root_observation = observation[:] - + visited = set() # Start from the current orientation, and see which transitions are available; # organize them as [left, forward, right, back], relative to the current orientation # If only one transition is possible, the tree is oriented with this transition as the forward branch. @@ -219,8 +219,10 @@ class TreeObsForRailEnv(ObservationBuilder): if possible_transitions[branch_direction]: new_cell = self._new_position(agent.position, branch_direction) - branch_observation = self._explore_branch(handle, new_cell, branch_direction, root_observation, 1) + branch_observation, branch_visited = \ + self._explore_branch(handle, new_cell, branch_direction, root_observation, 1) observation = observation + branch_observation + visited = visited.union(branch_visited) else: num_cells_to_fill_in = 0 pow4 = 1 @@ -228,6 +230,7 @@ class TreeObsForRailEnv(ObservationBuilder): num_cells_to_fill_in += pow4 pow4 *= 4 observation = observation + [-np.inf, -np.inf, -np.inf, -np.inf, -np.inf] * num_cells_to_fill_in + self.env.dev_obs_dict[handle] = visited return observation def _explore_branch(self, handle, position, direction, root_observation, depth): @@ -236,7 +239,7 @@ class TreeObsForRailEnv(ObservationBuilder): """ # [Recursive branch opened] if depth >= self.max_depth + 1: - return [] + return [], [] # Continue along direction until next switch or # until no transitions are possible along the current direction (i.e., dead-ends) @@ -377,22 +380,24 @@ class TreeObsForRailEnv(ObservationBuilder): # Swap forward and back in case of dead-end, so that an agent can learn that going forward takes # it back new_cell = self._new_position(position, (branch_direction + 2) % 4) - branch_observation = self._explore_branch(handle, - new_cell, - (branch_direction + 2) % 4, - new_root_observation, - depth + 1) + branch_observation, branch_visited = self._explore_branch(handle, + new_cell, + (branch_direction + 2) % 4, + new_root_observation, + depth + 1) observation = observation + branch_observation - + if len(branch_visited) != 0: + visited = visited.union(branch_visited) elif last_isSwitch and possible_transitions[branch_direction]: new_cell = self._new_position(position, branch_direction) - branch_observation = self._explore_branch(handle, - new_cell, - branch_direction, - new_root_observation, - depth + 1) + branch_observation, branch_visited = self._explore_branch(handle, + new_cell, + branch_direction, + new_root_observation, + depth + 1) observation = observation + branch_observation - + if len(branch_visited) != 0: + visited = visited.union(branch_visited) else: num_cells_to_fill_in = 0 pow4 = 1 @@ -401,7 +406,7 @@ class TreeObsForRailEnv(ObservationBuilder): pow4 *= 4 observation = observation + [-np.inf, -np.inf, -np.inf, -np.inf, -np.inf] * num_cells_to_fill_in - return observation + return observation, visited def util_print_obs_subtree(self, tree, num_features_per_node=5, prompt='', current_depth=0): """ diff --git a/flatland/envs/rail_env.py b/flatland/envs/rail_env.py index a5248a8fb3b5a7ee2d433aa12c5daca84753e6e9..118ebf4d3ea122519a09c8b7b5a00c53964063a1 100644 --- a/flatland/envs/rail_env.py +++ b/flatland/envs/rail_env.py @@ -98,7 +98,7 @@ class RailEnv(Environment): self.obs_dict = {} self.rewards_dict = {} - + self.dev_obs_dict = {} # self.agents_handles = list(range(self.number_of_agents)) # self.agents_position = [] @@ -315,6 +315,7 @@ class RailEnv(Environment): def _get_observations(self): self.obs_dict = {} + self.debug_obs_dict = {} # for handle in self.agents_handles: for iAgent in range(self.get_num_agents()): self.obs_dict[iAgent] = self.obs_builder.get(iAgent) @@ -328,6 +329,11 @@ class RailEnv(Environment): grid_data = self.rail.grid.tolist() agent_static_data = [agent.to_list() for agent in self.agents_static] agent_data = [agent.to_list() for agent in self.agents] + + msgpack.packb(grid_data) + msgpack.packb(agent_data) + msgpack.packb(agent_static_data) + msg_data = { "grid": grid_data, "agents_static": agent_static_data, diff --git a/flatland/utils/rendertools.py b/flatland/utils/rendertools.py index 4921def4218beef21e119a3979bbb88c02af984d..34f3e9fa6857e86f4d99d211784d983a2e2a1e75 100644 --- a/flatland/utils/rendertools.py +++ b/flatland/utils/rendertools.py @@ -10,6 +10,7 @@ from flatland.utils.render_qt import QTGL, QTSVG from flatland.utils.graphics_pil import PILGL from flatland.utils.graphics_layer import GraphicsLayer + # TODO: suggested renaming to RailEnvRenderTool, as it will only work with RailEnv! @@ -100,12 +101,11 @@ class RenderTool(object): lColors = list("brgcmyk") # \delta RC for NESW gTransRC = np.array([[-1, 0], [0, 1], [1, 0], [0, -1]]) - nPixCell = 1 # misnomer... + nPixCell = 1 # misnomer... nPixHalf = nPixCell / 2 xyHalf = array([nPixHalf, -nPixHalf]) grc2xy = array([[0, -nPixCell], [nPixCell, 0]]) - gGrid = array(np.meshgrid(np.arange(10), -np.arange(10))) * \ - array([[[nPixCell]], [[nPixCell]]]) + gGrid = array(np.meshgrid(np.arange(10), -np.arange(10))) * array([[[nPixCell]], [[nPixCell]]]) # xyPixHalf = xr.DataArray([nPixHalf, -nPixHalf], # dims="xy", # coords={"xy": ["x", "y"]}) @@ -130,7 +130,7 @@ class RenderTool(object): self.gl = PILGL(env.width, env.height) elif gl == "QTSVG": self.gl = QTSVG(env.width, env.height) - + self.new_rail = True def set_new_rail(self): @@ -153,14 +153,14 @@ class RenderTool(object): def plotAgents(self, targets=True, iSelectedAgent=None): cmap = self.gl.get_cmap('hsv', - lut=max(len(self.env.agents), len(self.env.agents_static) + 1)) + lut=max(len(self.env.agents), len(self.env.agents_static) + 1)) for iAgent, agent in enumerate(self.env.agents_static): if agent is None: continue oColor = cmap(iAgent) self.plotAgent(agent.position, agent.direction, oColor, target=agent.target if targets else None, - static=True, selected=iAgent == iSelectedAgent) + static=True, selected=iAgent == iSelectedAgent) for iAgent, agent in enumerate(self.env.agents): if agent is None: @@ -211,8 +211,8 @@ class RenderTool(object): """ rt = self.__class__ - rcDir = rt.gTransRC[iDir] # agent direction in RC - xyDir = np.matmul(rcDir, rt.grc2xy) # agent direction in xy + rcDir = rt.gTransRC[iDir] # agent direction in RC + xyDir = np.matmul(rcDir, rt.grc2xy) # agent direction in xy xyPos = np.matmul(rcPos - rcDir / 2, rt.grc2xy) + rt.xyHalf @@ -220,7 +220,7 @@ class RenderTool(object): color = self.gl.adaptColor(color, lighten=True) # print("Agent:", rcPos, iDir, rcDir, xyDir, xyPos) - self.gl.scatter(*xyPos, color=color, marker="o", s=100) # agent location + self.gl.scatter(*xyPos, color=color, marker="o", s=100) # agent location xyDirLine = array([xyPos, xyPos + xyDir / 2]).T # line for agent orient. self.gl.plot(*xyDirLine, color=color, lw=5, ms=0, alpha=0.6) if selected: @@ -398,7 +398,7 @@ class RenderTool(object): xyPrev = xy def drawTrans2( - self, + self, xyLine, xyCentre, rotation, bDeadEnd=False, sColor="gray", @@ -420,9 +420,9 @@ class RenderTool(object): if sColor == "auto": if dx > 0 or dy > 0: - sColor = "C1" # N or E + sColor = "C1" # N or E else: - sColor = "C2" # S or W + sColor = "C2" # S or W if bDeadEnd: xyLine2 = array([ @@ -472,12 +472,29 @@ class RenderTool(object): xyMid + [-dx + dy, -dx - dy]]) self.gl.plot(*xyArrow.T, color=sColor) + def renderObs(self, agent_handles, observation_dict): + """ + Render the extent of the observation of each agent. All cells that appear in the agent obsrevation will be + highlighted. + :param agent_handles: List of agent indices to adapt color and get correct observation + :param observation_dict: dictionary containing sets of cells of the agent observation + + """ + rt = self.__class__ + + cmap = self.gl.get_cmap('hsv', lut=max(len(self.env.agents), len(self.env.agents_static) + 1)) + + for agent in agent_handles: + color = cmap(agent) + for visited_cell in observation_dict[agent]: + cell_coord = array(visited_cell[:2]) + cell_coord_trans = np.matmul(cell_coord, rt.grc2xy) + rt.xyHalf + self._draw_square(cell_coord_trans, 1 / 3, color) + def renderEnv( - self, show=False, curves=True, spacing=False, - arrows=False, agents=True, sRailColor="gray", - frames=False, iEpisode=None, iStep=None, - iSelectedAgent=None, - action_dict=None): + self, show=False, curves=True, spacing=False, + arrows=False, agents=True, obsrender=True, sRailColor="gray", frames=False, iEpisode=None, iStep=None, + iSelectedAgent=None, action_dict=None): """ Draw the environment using matplotlib. Draw into the figure if provided. @@ -488,15 +505,16 @@ class RenderTool(object): if not self.gl.is_raster(): self.renderEnv2(show, curves, spacing, - arrows, agents, sRailColor, - frames, iEpisode, iStep, - iSelectedAgent, action_dict) + arrows, agents, sRailColor, + frames, iEpisode, iStep, + iSelectedAgent, action_dict) return # cell_size is a bit pointless with matplotlib - it does not relate to pixels, # so for now I've changed it to 1 (from 10) cell_size = 1 self.gl.beginFrame() + # self.gl.clf() # if oFigure is None: # oFigure = self.gl.figure() @@ -528,9 +546,9 @@ class RenderTool(object): for c in range(env.width): # bounding box of the grid cell - x0 = cell_size * c # left - x1 = cell_size * (c + 1) # right - y0 = cell_size * -r # top + x0 = cell_size * c # left + x1 = cell_size * (c + 1) # right + y0 = cell_size * -r # top y1 = cell_size * -(r + 1) # bottom # centres of cell edges @@ -538,7 +556,7 @@ class RenderTool(object): ((x0 + x1) / 2.0, y0), # N middle top (x1, (y0 + y1) / 2.0), # E middle right ((x0 + x1) / 2.0, y1), # S middle bottom - (x0, (y0 + y1) / 2.0) # W middle left + (x0, (y0 + y1) / 2.0) # W middle left ] # cell centre @@ -611,7 +629,8 @@ class RenderTool(object): # Draw each agent + its orientation + its target if agents: self.plotAgents(targets=True, iSelectedAgent=iSelectedAgent) - + if obsrender: + self.renderObs(range(env.get_num_agents()), env.dev_obs_dict) # Draw some textual information like fps yText = [-0.3, -0.6, -0.9] if frames: @@ -665,18 +684,18 @@ class RenderTool(object): gP0 = array([[0, 0, 0]]).T nDepth = 2 for i in range(nDepth): - nDepthNodes = nBranchFactor**i + nDepthNodes = nBranchFactor ** i # rScale = nBranchFactor ** (nDepth - i) - rShrinkDepth = 1/(i+1) + rShrinkDepth = 1 / (i + 1) # gX1 = np.linspace(-nDepthNodes / 2, nDepthNodes / 2, nDepthNodes) * rShrinkDepth - - gX1 = np.linspace(-(nDepthNodes-1), (nDepthNodes-1), nDepthNodes) * rShrinkDepth + + gX1 = np.linspace(-(nDepthNodes - 1), (nDepthNodes - 1), nDepthNodes) * rShrinkDepth gY1 = np.ones((nDepthNodes)) * i gZ1 = np.zeros((nDepthNodes)) - + gP1 = array([gX1, gY1, gZ1]) gP01 = np.append(gP0, gP1, axis=1) - + if nDepthNodes > 1: nDepthNodesPrev = nDepthNodes / nBranchFactor giP0 = np.repeat(np.arange(nDepthNodesPrev), nBranchFactor) @@ -687,12 +706,10 @@ class RenderTool(object): self.gl.plot(gP01[0], -gP01[1], lines=giLinePoints, color="gray") gP0 = array([gX1, gY1, gZ1]) - + def renderEnv2( - self, show=False, curves=True, spacing=False, - arrows=False, agents=True, sRailColor="gray", - frames=False, iEpisode=None, iStep=None, - iSelectedAgent=None, + self, show=False, curves=True, spacing=False, arrows=False, agents=True, renderobs=True, sRailColor="gray", + frames=False, iEpisode=None, iStep=None, iSelectedAgent=None, action_dict=dict()): """ Draw the environment using matplotlib. @@ -710,12 +727,11 @@ class RenderTool(object): # Draw each cell independently for r in range(env.height): for c in range(env.width): - binTrans = env.rail.grid[r, c] self.gl.setRailAt(r, c, binTrans) cmap = self.gl.get_cmap('hsv', - lut=max(len(self.env.agents), len(self.env.agents_static) + 1)) + lut=max(len(self.env.agents), len(self.env.agents_static) + 1)) for iAgent, agent in enumerate(self.env.agents): if agent is None: @@ -729,14 +745,14 @@ class RenderTool(object): if iAgent in action_dict: iAction = action_dict[iAgent] new_direction, action_isValid = self.env.check_action(agent, iAction) - + if action_isValid: self.gl.setAgentAt(iAgent, *agent.position, agent.direction, new_direction, color=oColor) else: pass # print("invalid action - agent ", iAgent, " bend ", agent.direction, new_direction) # self.gl.setAgentAt(iAgent, *agent.position, agent.direction, new_direction) - + self.gl.show() for i in range(3): self.gl.processEvents() diff --git a/setup.py b/setup.py index 96afbe85dc99aa47533936d2a001b9d74717ec58..d14070351559b5e59ed677751b70c20891dbc2f4 100644 --- a/setup.py +++ b/setup.py @@ -2,7 +2,7 @@ # -*- coding: utf-8 -*- """The setup script.""" - +import os from setuptools import setup, find_packages with open('README.rst') as readme_file: @@ -11,11 +11,19 @@ with open('README.rst') as readme_file: with open('HISTORY.rst') as history_file: history = history_file.read() -requirements = ['Click>=6.0', ] - -setup_requirements = ['pytest-runner', ] - -test_requirements = ['pytest', ] +# Gather requirements from requirements_dev.txt +# TODO : We could potentially split up the test/dev dependencies later +install_reqs = [] +requirements_path = 'requirements_dev.txt' +with open(requirements_path, 'r') as f: + install_reqs += [ + s for s in [ + line.strip(' \n') for line in f + ] if not s.startswith('#') and s != '' + ] +requirements = install_reqs +setup_requirements = install_reqs +test_requirements = install_reqs setup( author="S.P. Mohanty", diff --git a/tests/test_env_observation_builder.py b/tests/test_env_observation_builder.py index 1692a98982a787ecbadf79feb7a35593f82522e4..13f8d8f9d7df1808bfb69261ee25bf06f9da2d45 100644 --- a/tests/test_env_observation_builder.py +++ b/tests/test_env_observation_builder.py @@ -3,7 +3,7 @@ import numpy as np -from flatland.core.env_observation_builder import GlobalObsForRailEnv +from flatland.envs.observations import GlobalObsForRailEnv from flatland.core.transition_map import GridTransitionMap, Grid4Transitions from flatland.envs.rail_env import RailEnv from flatland.envs.generators import rail_from_GridTransitionMap_generator diff --git a/tests/test_environments.py b/tests/test_environments.py index 4c55eac7afb44d95f0e49d665eeeb4bc36becea9..9c7b53b9b5876a99d7deea20da10816d81f02b65 100644 --- a/tests/test_environments.py +++ b/tests/test_environments.py @@ -7,7 +7,7 @@ from flatland.envs.generators import rail_from_GridTransitionMap_generator from flatland.envs.generators import complex_rail_generator from flatland.core.transitions import Grid4Transitions from flatland.core.transition_map import GridTransitionMap -from flatland.core.env_observation_builder import GlobalObsForRailEnv +from flatland.envs.observations import GlobalObsForRailEnv from flatland.envs.agent_utils import EnvAgent """Tests for `flatland` package.""" diff --git a/tests/test_rendertools.py b/tests/test_rendertools.py index f6defb2de400f1a007da237b2f91ec38df5db07b..245f2f327524653b3cf03bf921f6db6b0d4b51fb 100644 --- a/tests/test_rendertools.py +++ b/tests/test_rendertools.py @@ -6,10 +6,10 @@ Tests for `flatland` package. from flatland.envs.rail_env import RailEnv, random_rail_generator import numpy as np -<<<<<<< HEAD -======= +#<<<<<<< HEAD +#======= # import os ->>>>>>> dc2fa1ee0244b15c76d89ab768c5e1bbd2716147 +#>>>>>>> dc2fa1ee0244b15c76d89ab768c5e1bbd2716147 import sys import matplotlib.pyplot as plt diff --git a/tox.ini b/tox.ini index 5a97b7e1c5b2f09db2cca8da5e1cf9db002f26f1..1c9a170724b2ed111eac11061ba598eea52a571c 100644 --- a/tox.ini +++ b/tox.ini @@ -23,7 +23,10 @@ commands = make docs [testenv:coverage] basepython = python whitelist_externals = make -commands = make coverage +commands = + pip install -U pip + pip install -r requirements_dev.txt + make coverage [testenv] setenv = @@ -35,6 +38,7 @@ deps = ; -r{toxinidir}/requirements.txt commands = pip install -U pip + pip install -r requirements_dev.txt py.test --basetemp={envtmpdir}