How to solve this : Calculated padded input size per channel: (3 x 3). Kernel size: (4 x 4). Kernel size can't be greater than actual input size - dcgan

I have problem : Calculated padded input size per channel: (3 x 3). Kernel size: (4 x 4). Kernel size can't be greater than actual input size
def conv(c_in, c_out, batch_norm=True, activation="lrelu"):
return conv_block(c_in, c_out, kernel=4, stride=2, pad=1, bias=False, batch_norm=batch_norm, activation=activation, pool_type=None)
def tconv(c_in, c_out, batch_norm=True, activation="lrelu"):
return tconv_block(c_in, c_out, kernel=4, stride=2, pad=1, bias=False, batch_norm=batch_norm, activation=activation, pool_type=None)
def __init__(self):
super().__init__()
self.conv = nn.Sequential(
conv(3, 32, batch_norm=False),
conv(32, 64),
conv(64, 128),
conv(128, 256),
conv_block(256, 1, kernel=4, stride=1, pad=0, bias=False, activation=None, pool_type=None),
nn.Flatten()
)
def forward(self, x):
x = self.conv(x)
return x
def clip_weights(self, vmin=-0.01, vmax=0.01):
for p in self.parameters():
p.data.clamp_(vmin, vmax)
class Generator(nn.Module):
def __init__(self, z_dim):
super().__init__()
self.z_dim = z_dim
self.tconv = nn.Sequential(
tconv_block(z_dim, 512, kernel=4, stride=2, pad=1, bias=False, activation="lrelu", pool_type=None),
tconv(512, 256),
tconv(256, 128),
tconv(128, 64),
tconv(64, 32),
tconv(32, 3, activation="tanh", batch_norm=False)
)
def forward(self, x):
return self.tconv(x)
def generate(self, n, device):
z = torch.randn((n, self.z_dim, 1, 1), device=device)
return self.tconv(z)```

z = torch.randn((n, self.z_dim, 1, 1), device=device)
The code above generates input noise tensor with (1,1) size, which is too small for the model.
z = torch.randn((n, self.z_dim, 10, 10), device=device)
Increasing the size of the input tensor, as in the code above, should solve the error.

Related

Pytorch geometric label prediction using GNN

I'm currently trying to find a way how to get a single label prediction from my GNN.I'd like to create a list of ground truths compared to how the model predicts the label.
My code is as following, ran in google collab:
Should I create a validation step or is there another way about this? I'm very new to this.
# Load the dataset
dataset = TUDataset(
root=".", name="Mutagenicity",
transform=AddSymbols(),
).shuffle()
from torch_geometric.data import DataLoader
loader = DataLoader(dataset, batch_size=32, shuffle=True)
data_batch = next(iter(loader))
print(data_batch)
print("Number of graphs in the batch:",data_batch.num_graphs)
import torch
from torch_scatter import scatter
from torch_geometric.utils import to_dense_adj
class GraphConvolution(torch.nn.Module):
def __init__(self, num_input_features, num_output_features):
super().__init__()
self.W1 = torch.nn.Parameter(torch.randn(num_input_features, num_output_features)*0.1)
self.W2 = torch.nn.Parameter(torch.randn(num_input_features, num_output_features)*0.1)
def forward(self, x, edge_index):
adj = to_dense_adj(edge_index)
neighbors_aggregation = adj # x
out = x # self.W1 + neighbors_aggregation # self.W2
return out
from torch.nn import Linear, LogSoftmax
import torch.nn.functional as F
from torch_geometric.nn import GraphConv, global_add_pool
class GNN(torch.nn.Module):
def __init__(
self,
num_classes,
hidden_dim,
node_features_dim,
edge_features_dim=None
):
super(GNN, self).__init__()
self.hidden_dim = hidden_dim
self.conv1 = GraphConv(node_features_dim, hidden_dim)
self.conv2 = GraphConv(hidden_dim, hidden_dim)
self.conv3 = GraphConv(hidden_dim, hidden_dim)
self.conv4 = GraphConv(hidden_dim, hidden_dim)
self.conv5 = GraphConv(hidden_dim, hidden_dim)
self.fc1 = Linear(hidden_dim, hidden_dim)
self.fc2 = Linear(hidden_dim, num_classes)
self.readout = LogSoftmax(dim=-1)
def forward(self, x, edge_index, batch):
x = F.relu(self.conv1(x, edge_index))
x = F.relu(self.conv2(x, edge_index))
x = F.relu(self.conv3(x, edge_index))
x = F.relu(self.conv4(x, edge_index))
x = F.relu(self.conv5(x, edge_index))
x = global_add_pool(x, batch)
x = F.relu(self.fc1(x))
x = F.dropout(x, p=0.5, training=self.training)
x = self.fc2(x)
return self.readout(x)
from torch_geometric.loader import DataLoader
from tqdm.auto import tqdm
# If possible, we use a GPU
device = "cuda" if torch.cuda.is_available() else "cpu"
print("Using device:", device)
idx_train_end = int(len(dataset) * .5)
idx_valid_end = int(len(dataset) * .7)
BATCH_SIZE = 128
BATCH_SIZE_TEST = len(dataset) - idx_valid_end
# In the test loader we set the natch size to be equal to the size of the whole test set
loader_train = DataLoader(dataset[:idx_train_end], batch_size=BATCH_SIZE, shuffle=True)
loader_valid = DataLoader(dataset[idx_train_end:idx_valid_end], batch_size=BATCH_SIZE, shuffle=True)
loader_test = DataLoader(dataset[idx_valid_end:], batch_size=BATCH_SIZE_TEST, shuffle=False)
from src import compute_accuracy
def train_model(loader_train, loader_valid, model, optimizer, loss_function, N_EPOCHS):
# Prepare empy lists for logging
train_losses = []
train_accs = []
val_accs = []
for epoch in tqdm(range(N_EPOCHS)):
epoch_loss = 0
for batch in tqdm(loader_train, leave=False):
batch.to(device)
out = model(batch.x, batch.edge_index, batch.batch)
loss = loss_function(out, batch.y.flatten())
optimizer.zero_grad()
loss.backward()
optimizer.step()
acc_train = compute_accuracy(model, loader_train)
acc_valid = compute_accuracy(model, loader_valid)
with torch.no_grad():
train_accs.append(acc_train)
val_accs.append(acc_valid)
train_losses.append(loss)
print(f"Epoch: {epoch}, Loss: {loss}")

deconvolution (convTranspose2D) intialization with bilinear interpolation in pytorch?

I am running PyTorch implementation of this article (https://arxiv.org/pdf/1604.03650.pdf).
In the article, it says to initialize the deconv layers with bilinear interpolation which is not in the code. it is told that if in deconvolution, we are scaling the data by factor S, the initial weights are defined as:
enter image description here
Does anyone know how can I implement it?
This is the neural network model designed based on the article:
In another word, I don't know how to initialization the deconvolution layers (for example deconv1 layer in this code).
import torch
import torch.nn as nn
import numpy as np
import torchvision
import torch.nn.functional as F
cfg = [64, 128, 256, 512, 512]
class Deep3d(nn.Module):
def __init__(self, in_channels=3, out_channels=3, device=torch.device('cpu')):
super(Deep3d, self).__init__()
self.device = device
vgg16 = torchvision.models.vgg16_bn(pretrained=True)
modules = []
layer = []
for l in vgg16.features:
if isinstance(l, nn.MaxPool2d):
layer.append(l)
modules.append(layer)
layer = []
else:
layer.append(l)
scale = 1
deconv = []
layer = []
for m in range(len(modules)):
layer.append(nn.Conv2d(cfg[m], cfg[m], kernel_size=3, stride=1, padding=True))
layer.append(nn.ReLU(inplace=True))
layer.append(nn.Conv2d(cfg[m], cfg[m], kernel_size=3, stride=1, padding=True))
layer.append(nn.ReLU(inplace=True))
if(m==0):
layer.append(nn.ConvTranspose2d(cfg[m], 65, kernel_size=1, stride=1, padding=(0,0)))
else:
scale *=2
layer.append(nn.ConvTranspose2d(cfg[m], 65, kernel_size=scale*2, stride=scale, padding=(scale//2, scale//2)))
deconv.append(layer) # add blocks of layers to deconv part of the network
layer = []
self.module_1 = nn.Sequential(*modules[0])
self.module_2 = nn.Sequential(*modules[1])
self.module_3 = nn.Sequential(*modules[2])
self.module_4 = nn.Sequential(*modules[3])
self.module_5 = nn.Sequential(*modules[4])
self.deconv_1 = nn.Sequential(*deconv[0])
self.deconv_2 = nn.Sequential(*deconv[1])
self.deconv_3 = nn.Sequential(*deconv[2])
self.deconv_4 = nn.Sequential(*deconv[3])
self.deconv_5 = nn.Sequential(*deconv[4])
self.linear_module = nn.Sequential(*[nn.Linear(15360,4096), # hyperparam choice
nn.ReLU(inplace=True),
nn.Dropout(p=0.5),
nn.Linear(4096,1950)]) # 1950=65(disparity range)*10*3(10*3 is feature map size)
self.deconv_6 = nn.Sequential(*[nn.ConvTranspose2d(65,65,kernel_size=scale*2,stride=scale,padding=(scale//2,scale//2))])
self.upconv_final = nn.Sequential(*[nn.ConvTranspose2d(65,65,kernel_size=(4,4),stride=2,padding=(1,1)),
nn.ReLU(inplace=True),
nn.Conv2d(65,65,kernel_size=(3,3),stride=1,padding=(1,1)),
nn.Softmax(dim=1)])
for block in [self.deconv_1,self.deconv_2,self.deconv_3,self.deconv_4,self.deconv_5,self.deconv_6,self.linear_module,self.upconv_final]:
for m in block:
if isinstance(m, nn.Conv2d):
nn.init.kaiming_normal_(m.weight, mode='fan_out', nonlinearity='relu')
if m.bias is not None:
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.BatchNorm2d):
nn.init.constant_(m.weight, 1)
nn.init.constant_(m.bias, 0)
elif isinstance(m, nn.Linear):
nn.init.normal_(m.weight, 0, 0.01)
nn.init.constant_(m.bias, 0)
def forward(self, orig_x, x):
x_copy = orig_x
pred = []
out_1 = self.module_1(x)
out_2 = self.module_2(out_1)
out_3 = self.module_3(out_2)
out_4 = self.module_4(out_3)
out_5 = self.module_5(out_4)
# print(out_5.shape)
out_5_flatten = out_5.view(x_copy.shape[0],-1)
out_6 = self.linear_module(out_5_flatten)
p1 = self.deconv_1(out_1)
p2 = self.deconv_2(out_2)
p3 = self.deconv_3(out_3)
p4 = self.deconv_4(out_4)
p5 = self.deconv_5(out_5)
# print(p5.shape)
p6 = self.deconv_6(out_6.view(x_copy.shape[0],65,3,10))
pred.append(p1)
pred.append(p2)
pred.append(p3)
pred.append(p4)
pred.append(p5)
pred.append(p6)
out = torch.zeros(pred[0].shape).to(self.device)
for p in pred:
out = torch.add(out, p)
out = self.upconv_final(out) # to be elt wise multiplied with shifted left views
out = F.interpolate(out,scale_factor=4,mode='bilinear') # upscale to match left input size
new_right_image = torch.zeros(x_copy.size()).to(self.device)
stacked_shifted_view = None
stacked_out = None
for depth_map_idx in range(-33,32):
shifted_input_view = torch.zeros(x_copy.size()).to(self.device)
if depth_map_idx<0:
shifted_input_view[:,:,:,:depth_map_idx] = x_copy[:,:,:,-depth_map_idx:]
elif depth_map_idx==0:
shifted_input_view = x_copy
else:
shifted_input_view[:,:,:,depth_map_idx:] = x_copy[:,:,:,:-depth_map_idx]
if stacked_shifted_view is None:
stacked_shifted_view = shifted_input_view.unsqueeze(1)
else:
stacked_shifted_view = torch.cat((stacked_shifted_view,shifted_input_view.unsqueeze(1)),dim=1)
if stacked_out is None:
stacked_out = out[:,depth_map_idx+33:depth_map_idx+34,:,:].unsqueeze(1)
else:
stacked_out = torch.cat((stacked_out,out[:,depth_map_idx+33:depth_map_idx+34,:,:].unsqueeze(1)),dim=1)
softmaxed_stacked_shifted_view = stacked_shifted_view
mult_soft_shift_out = torch.mul(stacked_out,softmaxed_stacked_shifted_view)
final_rt_image = torch.sum(mult_soft_shift_out,dim=1)
return final_rt_image
# if(__name__=='__main__'):
# vgg16 = torchvision.models.vgg16(pretrained=True)
# model = Deep3d().to(torch.device('cpu'))
# out = model(torch.randn(10,3,384,1280),torch.randn(10,3,96,320))

How to embed plot inside wx.SplitterWindow (right panel)?

I'm trying to embed plot inside right panel of Splitter window, how to add plot inside splitter window. please find here the link for the dataset.
https://www.dropbox.com/s/ncy6dlpm79p578s/Dataset.zip?dl=0.
The file contains rows and columns of wavelength and reflectance.
import wx
from pylab import *
import asciitable
import matplotlib.pyplot as plt
import os
from wxmplot import ImageMatrixFrame
class RandomObj(object):
def __init__(self, name):
self.name = name
class SLI(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, size=(820, 450))
splitter = wx.SplitterWindow(self, style = wx.SP_BORDER)
leftPanel = wx.Panel(splitter, size=(400,100))
rightPanel = wx.Panel(splitter, size=(400,100))
####Tree Widget#####
self.tree = wx.TreeCtrl(leftPanel)
leftSizer = wx.BoxSizer(wx.VERTICAL)
leftSizer.Add(self.tree, 1, wx.EXPAND | wx.ALIGN_CENTER)
leftPanel.SetSizer(leftSizer)
rightSizer = wx.BoxSizer(wx.VERTICAL)
self.display = wx.StaticText(rightPanel, -1, '', (10, 10),
style=wx.ALIGN_CENTRE)
rightSizer.Add(self.display, -1, wx.EXPAND)
rightPanel.SetSizer(rightSizer)
splitter.SplitVertically(leftPanel, rightPanel)
##### Splitter ends ####
root = self.tree.AddRoot('Database')
self.tree.AppendItem(root, 'USGS')
files = []
self.dname = []
self.test = []
for dirname, dirnames, filenames in os.walk('.\USGS'):
for filename in filenames:
files.append(os.path.join(dirname, filename))
self.test.append(filename)
self.tree.AppendItem(self.tree.GetLastChild(root), filename)
self.dname = files[:]
self.tree.AppendItem(root,'ASTER')
for dirname, dirnames, filenames in os.walk('.\ASTER'):
for filename in filenames:
files.append(os.path.join(dirname, filename))
self.test.append(filename)
self.tree.AppendItem(self.tree.GetLastChild(root), filename)
self.dname = files[:]
self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.ASTER, self.tree)
def ASTER(self, event):
self.item = event.GetItem()
value1 = self.tree.GetItemText(self.item)
value2 = 0
value3 = 1
self.item=None
for k in self.test:
if value1 == k:
value2 +=1
break
else:
value2 +=1
for i in self.dname:
if value3 == value2:
array =[]
f=open(i, 'r')
for j in xrange(27):
f.next()
for line in f:
array.append(line)
data = asciitable.read(array)
plot(data.col1, data.col2)
title(value1)
show()
break
else:
value3 +=1
app = wx.App(None)
frame = ImageMatrixFrame()
SLI().Show()
app.MainLoop()
how to insert plot window inside right panel of splitter.
I am not 100% sure I understand your code - there are some formatting and indentation problems for sure. I also am not familiar with asciitable. But, that said, a wxmplot.PlotPanel or ImagePanel can be embedded in a wxPython Frame that uses a Splitter. An example might look like the code below. I tried to make it short, but also tried to make it complete and using plain wxPython. For a more complete application, you'd probably want to put the reading of the datafiles into a separate class, etc. Anyway, this uses your Dataset folder, and should mostly work to show the concepts:
#!/usr/bin/env python
import os
import wx
from wxmplot import PlotPanel
# see https://gist.github.com/newville/e805a6454c4e4c0e010bf0b3cc796d52
from asciifile import read_ascii
LEFTSTYLE = wx.ALIGN_LEFT|wx.GROW|wx.ALL
def pack(window, sizer, expand=1.1):
"simple wxPython pack function"
tsize = window.GetSize()
msize = window.GetMinSize()
window.SetSizer(sizer)
sizer.Fit(window)
nsize = (int(1.1*max(msize[0], tsize[0])),
int(1.1*max(msize[1], tsize[1])))
window.SetSize(nsize)
class SpectraPlotterFrame(wx.Frame):
def __init__(self, data_folder):
wx.Frame.__init__(self, None, size=(800, 450))
self.SetTitle("Data File Plotter: {:s}".format(data_folder))
self.data_folder = data_folder
self.current_filename = None
splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
splitter.SetMinimumPaneSize(200)
# left side: ListBox of File Names
l_panel = wx.Panel(splitter)
l_sizer = wx.BoxSizer(wx.VERTICAL)
self.filelist = wx.ListBox(l_panel)
self.filelist.Bind(wx.EVT_LISTBOX, self.onFileChoice)
l_sizer.Add(self.filelist, 1, LEFTSTYLE, 5)
pack(l_panel, l_sizer)
# right side: Panel to choose plot array labels, make plot
r_panel = wx.Panel(splitter)
r_sizer = wx.GridBagSizer(3, 3)
self.xarr = wx.Choice(r_panel, choices=[], size=(175, -1))
self.yarr = wx.Choice(r_panel, choices=[], size=(175, -1))
xlabel = wx.StaticText(r_panel, label='X:', style=LEFTSTYLE)
ylabel = wx.StaticText(r_panel, label='Y:', style=LEFTSTYLE)
plot_btn = wx.Button(r_panel, label='Show Plot', size=(125, -1))
plot_btn.Bind(wx.EVT_BUTTON, self.onPlot)
self.plotpanel = PlotPanel(r_panel, size=(650, 450))
r_sizer.Add(xlabel, (0, 0), (1, 1), LEFTSTYLE, 2)
r_sizer.Add(self.xarr, (0, 1), (1, 1), LEFTSTYLE, 2)
r_sizer.Add(ylabel, (0, 2), (1, 1), LEFTSTYLE, 2)
r_sizer.Add(self.yarr, (0, 3), (1, 1), LEFTSTYLE, 2)
r_sizer.Add(plot_btn, (0, 4), (1, 1), LEFTSTYLE, 2)
r_sizer.Add(self.plotpanel, (1, 0), (1, 6), LEFTSTYLE, 2)
pack(r_panel, r_sizer)
splitter.SplitVertically(l_panel, r_panel, 1)
sizer = wx.BoxSizer(wx.VERTICAL)
sizer.Add(splitter, 1, LEFTSTYLE, 5)
pack(self, sizer)
wx.CallAfter(self.read_datafiles)
self.Show()
self.Raise()
def read_datafiles(self):
self.datasets = {}
dfolder = os.path.abspath(self.data_folder)
for fname in sorted(os.listdir(self.data_folder)):
try:
self.datasets[fname] = read_ascii(os.path.join(dfolder, fname))
except:
print("Could not read file {:s}".format(fname))
self.filelist.Append(fname)
def onFileChoice(self, event=None):
self.current_filename = fname = event.GetString()
for choice, default in ((self.xarr, 0), (self.yarr, 1)):
choice.Clear()
choice.AppendItems(self.datasets[fname].array_labels)
choice.SetSelection(default)
def onPlot(self, event=None):
x = self.xarr.GetSelection()
y = self.yarr.GetSelection()
xlab = self.xarr.GetStringSelection()
ylab = self.yarr.GetStringSelection()
if self.current_filename is not None:
dset = self.datasets[self.current_filename]
self.plotpanel.plot(dset.data[x], dset.data[y], xlabel=xlab,
ylabel=ylab, label=self.current_filename,
show_legend=True)
class SpectraPlotterApp(wx.App):
def __init__(self, data_folder='.', **kws):
self.data_folder = data_folder
wx.App.__init__(self, **kws)
def createApp(self):
frame = SpectraPlotterFrame(data_folder=self.data_folder)
self.SetTopWindow(frame)
def OnInit(self):
self.createApp()
return True
if __name__ == '__main__':
SpectraPlotterApp(data_folder='Dataset').MainLoop()

How to scale a QGraphicsItem without changing its position

I have a QGraphicsRect that changes its height dynamically. I want to normalize its height to 1 after its height changed. If I try to apply a QTransform or the scale() function, the y position of the QGraphicsRect changes as well if its y position is not 0.
I tried to move the QGraphicsRect back to the origin of the scene before applying the scaling, but it did not help. Any ideas?
Minimal working example below. (Press the button to change the height of the QRectItem). If the problem is solved, the QGraphicsRect upper and lower edge should always lay on the red lines.
import sys
import numpy as np
from PySide import QtCore, QtGui
class Test(QtGui.QMainWindow):
def __init__(self):
super(Test, self).__init__()
self.setupUi(self)
self.setupGV()
self.connectElements()
self.rectY = 3
self.initRect()
self.show()
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gv_center = QtGui.QGraphicsView(self.centralwidget)
self.gv_center.setGeometry(QtCore.QRect(100, 60, 561, 331))
self.gv_center.setObjectName("gv_center")
self.pb_debug = QtGui.QPushButton(self.centralwidget)
self.pb_debug.setGeometry(QtCore.QRect(540, 500, 94, 24))
self.pb_debug.setObjectName("pb_debug")
self.pb_debug.setText("push !")
MainWindow.setCentralWidget(self.centralwidget)
def connectElements(self):
self.pb_debug.clicked.connect(self.buttonClick)
def setupGV(self):
self.overviewScene = QtGui.QGraphicsScene(self)
self.overviewScene.setSceneRect(0, -0.5, 1, 7)
self.gv_center.setScene(self.overviewScene)
self.gv_center.fitInView(0, -0.5, 1, 7)
def initRect(self):
self.overviewScene.addLine(-5, self.rectY, 5, self.rectY, QtGui.QPen(QtGui.QColor(255, 0, 0)))
self.overviewScene.addLine(-5, self.rectY + 1, 5, self.rectY + 1, QtGui.QPen(QtGui.QColor(255, 0, 0)))
self.rect = self.overviewScene.addRect(0, self.rectY, 1, 1, QtGui.QPen(QtGui.QColor(0, 0, 0)))
def buttonClick(self):
newHeight = np.random.randint(1, 10)
print(newHeight)
geo = self.rect.rect()
geo.setHeight(newHeight)
self.rect.setRect(geo)
self.normalizeSubplot(self.rect, newHeight, self.rectY)
def normalizeSubplot(self, subplotItem, accH, y):
subplotItem.prepareGeometryChange()
if accH != 0:
height = 1.0 / accH
else:
height = 0
trans = subplotItem.transform()
trans.setMatrix(1, trans.m12(), trans.m13(),
trans.m21(), height, trans.m23(),
trans.m31(), trans.m32(), 1)
subplotItem.setTransform(trans)
if __name__ == "__main__":
app = QtGui.QApplication(sys.argv)
w = Test()
sys.exit(app.exec_())
After a couple of days I found the solution with help of some guy from IRC. The transformation matrix has to be:
trans.setMatrix(1, trans.m12(), trans.m13(),
trans.m21(), height, trans.m23(),
trans.m31(), -height * self.rectY + self.rectY, 1)

Matplotlib: Cursor snap to plotted data with datetime axis

I have a plot of 3 data sets that have datetime objetcs on the x axis.
I want to have a cursor that snaps to the data and shows the precise x and y value.
I already have a "snap to cursor", but that only works for scalar x axes.
Can anyone help me to modify the snap to cursor so that it works for datetime x axes as well?
Here are my data plots:
import numpy as np
import matplotlib.pyplot as plot
import matplotlib.ticker as mticker
import matplotlib.dates as dates
import datetime
import Helpers
fig = plot.figure(1)
DAU = ( 2, 20, 25, 60, 190, 210, 18, 196, 212)
WAU = ( 50, 160, 412, 403, 308, 379, 345, 299, 258)
MAU = (760, 620, 487, 751, 612, 601, 546, 409, 457)
firstDay = datetime.datetime(2012,1,15)
#create an array with len(DAU) entries from given starting day
dayArray = [firstDay + datetime.timedelta(days = i) for i in xrange(len(DAU))]
line1 = plot.plot(dayArray, DAU, 'o-', color = '#336699')
line2 = plot.plot(dayArray, WAU, 'o-', color = '#993333')
line3 = plot.plot(dayArray, MAU, 'o-', color = '#89a54e')
ax = plot.subplot(111)
dateLocator = mticker.MultipleLocator(2)
dateFormatter = dates.DateFormatter('%d.%m.%Y')
ax.xaxis.set_major_locator(dateLocator)
ax.xaxis.set_major_formatter(dateFormatter)
fig.autofmt_xdate(rotation = 90, ha = 'center')
yMax = max(np.max(DAU), np.max(WAU), np.max(MAU))
yLimit = 100 - (yMax % 100) + yMax
plot.yticks(np.arange(0, yLimit + 1, 100))
plot.title('Active users', weight = 'bold')
plot.grid(True, axis = 'both')
plot.subplots_adjust(bottom = 0.2)
plot.subplots_adjust(right = 0.82)
legend = plot.legend((line1[0], line2[0], line3[0]),
('DAU',
'WAU',
'MAU'),
'upper left',
bbox_to_anchor = [1, 1],
shadow = True)
frame = legend.get_frame()
frame.set_facecolor('0.80')
for t in legend.get_texts():
t.set_fontsize('small')
#THIS DOES NOT WORK
cursor = Helpers.SnaptoCursor(ax, dayArray, DAU, 'euro daily')
plot.connect('motion_notify_event', cursor.mouse_move)
plot.show()
And this is my module "Helper" that contains the "SnaptoCursor" class:
(I got the basic SnaptoCursor class from somewhere else and modified it a little bit)
from __future__ import print_function
import numpy as np
import matplotlib.pyplot as plot
def minsec(sec, unused):
"""
Returns a string of the input seconds formatted as mm'ss''.
"""
minutes = sec // 60
sec = sec - minutes * 60
return '{0:02d}\'{1:02d}\'\''.format(int(minutes), int(sec))
class SnaptoCursor():
"""
A cursor with crosshair snaps to the nearest x point.
For simplicity, I'm assuming x is sorted.
"""
def __init__(self, ax, x, y, formatting, z = None):
"""
ax: plot axis
x: plot spacing
y: plot data
formatting: string flag for desired formatting
z: optional second plot data
"""
self.ax = ax
self.lx = ax.axhline(color = 'k') #the horiz line
self.ly = ax.axvline(color = 'k') #the vert line
self.x = x
self.y = y
self.z = z
# text location in axes coords
self.txt = ax.text(0.6, 0.9, '', transform = ax.transAxes)
self.formatting = formatting
def format(self, x, y):
if self.formatting == 'minsec':
return 'x={0:d}, y='.format(x) + minsec(y, 0)
elif self.formatting == 'daily euro':
return u'day {0:d}: {1:.2f}€'.format(x, y)
def mouse_move(self, event):
if not event.inaxes: return
mouseX, mouseY = event.xdata, event.ydata
#searchsorted: returns an index or indices that suggest where x should be inserted
#so that the order of the list self.x would be preserved
indx = np.searchsorted(self.x, [mouseX])[0]
mouseX = self.x[indx]
#if z wasn't defined
if self.z == None:
mouseY = self.y[indx]
#if z was defined: compare the distance between mouse and the two plots y and z
#and use the nearest one
elif abs(mouseY - self.y[indx]) < abs(mouseY - self.z[indx]):
mouseY = self.y[indx]
else:
mouseY = self.z[indx]
#update the line positions
self.lx.set_ydata(mouseY)
self.ly.set_xdata(mouseX)
self.txt.set_text(self.format(mouseX, mouseY))
plot.draw()
Of course this does not work since I am calling the SnaptoCursor with the datetime array "dayArray", which is supposed to be compared to the mouse coordinates later on. And these data types are not comparable.
I got it!!!
The problems where these two lines in the init method of the SnaptoCursor class:
self.lx = ax.axhline(color = 'k') #the horiz line
self.ly = ax.axvline(color = 'k') #the vert line
They were somehow messing up the datetime x axis (that has ordinals up to 730,000 e.g.), so you just have to initialize the lines' coordinates:
self.lx = ax.axhline(y = min(y), color = 'k') #the horiz line
self.ly = ax.axvline(x = min(x), color = 'k') #the vert line
Then it works just fine!
I'll be posting my complete SnaptoCursor class now that I have modified so it accepts individual formatting strings, and it can take up to 3 input data plots - that get snapped to according to your mouse position.
def percent(x, unused):
"""
Returns a string of the float number x formatted as %.
"""
return '{0:1.2f}%'.format(x * 100)
def minsec(sec, unused):
"""
Returns a string of the input seconds formatted as mm'ss''.
"""
minutes = sec // 60
sec = sec - minutes * 60
return '{0:02d}\'{1:02d}\'\''.format(int(minutes), int(sec))
class SnaptoCursor():
"""
A cursor with crosshair snaps to the nearest x point.
For simplicity, I'm assuming x is sorted.
"""
def __init__(self, ax, x, y, formatting, y2 = None, y3 = None):
"""
ax: plot axis
x: plot spacing
y: plot data
formatting: string flag for desired formatting
y2: optional second plot data
y3: optional third plot data
"""
self.ax = ax
self.lx = ax.axhline(y = min(y), color = 'k') #the horiz line
self.ly = ax.axvline(x = min(x), color = 'k') #the vert line
self.x = x
self.y = y
self.y2 = y2
self.y3 = y3
# text location in axes coords
self.txt = ax.text(0.6, 0.9, '', transform = ax.transAxes)
self.formatting = formatting
def format(self, x, y):
if self.formatting == 'minsec':
return 'x={0:d}, y='.format(x) + minsec(y, 0)
if self.formatting == 'decimal':
return 'x={0:d}, y={1:d}'.format(x, int(y))
elif self.formatting == 'date decimal':
return 'x={0:%d.%m.%Y}, y={1:d}'.format(x, int(y))
elif self.formatting == 'decimal percent':
return 'x={0:d}, y={1:d}%'.format(x, int(y * 100))
elif self.formatting == 'float':
return 'x={0:d}, y={1:.2f}'.format(x, y)
elif self.formatting == 'float percent':
return 'x={0:d}, y='.format(x) + percent(y, 0)
elif self.formatting == 'daily euro':
return u'day {0:d}: {1:.2f}€'.format(x, y)
def mouse_move(self, event):
if not event.inaxes:
return
mouseX, mouseY = event.xdata, event.ydata
if type(self.x[0]) == datetime.datetime:
mouseX = dates.num2date(int(mouseX)).replace(tzinfo = None)
#searchsorted: returns an index or indices that suggest where mouseX should be inserted
#so that the order of the list self.x would be preserved
indx = np.searchsorted(self.x, [mouseX])[0]
#if indx is out of bounds
if indx >= len(self.x):
indx = len(self.x) - 1
#if y2 wasn't defined
if self.y2 == None:
mouseY = self.y[indx]
#if y2 was defined AND y3 wasn't defined
elif self.y3 == None:
if abs(mouseY - self.y[indx]) < abs(mouseY - self.y2[indx]):
mouseY = self.y[indx]
else:
mouseY = self.y2[indx]
#if y2 AND y3 were defined
elif abs(mouseY - self.y2[indx]) < abs(mouseY - self.y[indx]):
if abs(mouseY - self.y2[indx]) < abs(mouseY - self.y3[indx]):
mouseY = self.y2[indx]
else:
mouseY = self.y3[indx]
#lastly, compare y with y3
elif abs(mouseY - self.y[indx]) < abs(mouseY - self.y3[indx]):
mouseY = self.y[indx]
else:
mouseY = self.y3[indx]
#update the line positions
self.lx.set_ydata(mouseY)
self.ly.set_xdata(mouseX)
self.txt.set_text(self.format(mouseX, mouseY))
plot.draw()

Resources