diff --git a/.buildinfo b/.buildinfo new file mode 100644 index 000000000..edd893989 --- /dev/null +++ b/.buildinfo @@ -0,0 +1,4 @@ +# Sphinx build info version 1 +# This file records the configuration used when building these files. When it is not found, a full rebuild will be done. +config: 829241009113adc598a3d0a9865574af +tags: 645f666f9bcd5a90fca523b33c5a78b7 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 000000000..e69de29bb diff --git a/_images/examples_Pulse_Building_Tutorial_11_0.png b/_images/examples_Pulse_Building_Tutorial_11_0.png new file mode 100644 index 000000000..0ed28146c Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_11_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_11_1.png b/_images/examples_Pulse_Building_Tutorial_11_1.png new file mode 100644 index 000000000..bb3ae4b00 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_11_1.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_11_2.png b/_images/examples_Pulse_Building_Tutorial_11_2.png new file mode 100644 index 000000000..d2dd2604e Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_11_2.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_13_0.png b/_images/examples_Pulse_Building_Tutorial_13_0.png new file mode 100644 index 000000000..913bb112d Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_13_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_13_1.png b/_images/examples_Pulse_Building_Tutorial_13_1.png new file mode 100644 index 000000000..0a26b1105 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_13_1.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_15_0.png b/_images/examples_Pulse_Building_Tutorial_15_0.png new file mode 100644 index 000000000..05c93a52b Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_15_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_17_0.png b/_images/examples_Pulse_Building_Tutorial_17_0.png new file mode 100644 index 000000000..406aac3b0 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_17_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_21_0.png b/_images/examples_Pulse_Building_Tutorial_21_0.png new file mode 100644 index 000000000..4afe6d92c Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_21_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_23_0.png b/_images/examples_Pulse_Building_Tutorial_23_0.png new file mode 100644 index 000000000..08ba1e3f3 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_23_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_28_0.png b/_images/examples_Pulse_Building_Tutorial_28_0.png new file mode 100644 index 000000000..472825011 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_28_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_30_0.png b/_images/examples_Pulse_Building_Tutorial_30_0.png new file mode 100644 index 000000000..668f5b300 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_30_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_30_1.png b/_images/examples_Pulse_Building_Tutorial_30_1.png new file mode 100644 index 000000000..0ac4aff30 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_30_1.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_31_0.png b/_images/examples_Pulse_Building_Tutorial_31_0.png new file mode 100644 index 000000000..b2c7470ce Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_31_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_32_1.png b/_images/examples_Pulse_Building_Tutorial_32_1.png new file mode 100644 index 000000000..ce81c6619 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_32_1.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_32_2.png b/_images/examples_Pulse_Building_Tutorial_32_2.png new file mode 100644 index 000000000..41df762b0 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_32_2.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_4_0.png b/_images/examples_Pulse_Building_Tutorial_4_0.png new file mode 100644 index 000000000..1674eb671 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_4_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_5_0.png b/_images/examples_Pulse_Building_Tutorial_5_0.png new file mode 100644 index 000000000..d6883b0c8 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_5_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_5_1.png b/_images/examples_Pulse_Building_Tutorial_5_1.png new file mode 100644 index 000000000..8217e92f9 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_5_1.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_8_0.png b/_images/examples_Pulse_Building_Tutorial_8_0.png new file mode 100644 index 000000000..b26983f29 Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_8_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_9_0.png b/_images/examples_Pulse_Building_Tutorial_9_0.png new file mode 100644 index 000000000..eef6ec21a Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_9_0.png differ diff --git a/_images/examples_Pulse_Building_Tutorial_9_1.png b/_images/examples_Pulse_Building_Tutorial_9_1.png new file mode 100644 index 000000000..edbf4194a Binary files /dev/null and b/_images/examples_Pulse_Building_Tutorial_9_1.png differ diff --git a/_modules/broadbean/blueprint.html b/_modules/broadbean/blueprint.html new file mode 100644 index 000000000..fed482920 --- /dev/null +++ b/_modules/broadbean/blueprint.html @@ -0,0 +1,1259 @@ + + + + + + + + broadbean.blueprint - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.blueprint

+# This file is for defining the blueprint object
+
+import functools as ft
+import json
+import re
+import warnings
+from inspect import signature
+
+import numpy as np
+
+from .broadbean import PulseAtoms
+
+
+
+[docs] +class SegmentDurationError(Exception): + pass
+ + + +
+[docs] +class BluePrint: + """ + The class of a waveform to become. + """ + + def __init__( + self, + funlist=None, + argslist=None, + namelist=None, + marker1=None, + marker2=None, + segmentmarker1=None, + segmentmarker2=None, + SR=None, + durslist=None, + ): + """ + Create a BluePrint instance + + Args: + funlist (list): List of functions + argslist (list): List of tuples of arguments + namelist (list): List of names for the functions + marker1 (list): List of marker1 specification tuples + marker2 (list): List of marker2 specifiation tuples + durslist (list): List of durations + + Returns: + BluePrint + """ + # TODO: validate input + + # Sanitising + if funlist is None: + funlist = [] + if argslist is None: + argslist = [] + if namelist is None: + namelist = [] + if durslist is None: + durslist = [] + + # Are the lists of matching lengths? + lenlist = [len(funlist), len(argslist), len(namelist), len(durslist)] + + if any(elem != lenlist[0] for elem in lenlist): + raise ValueError( + f"All input lists must be of same length. Received lengths: {lenlist}" + ) + # Are the names valid names? + for name in namelist: + if not isinstance(name, str): + raise ValueError(f"All segment names must be strings. Received {name}.") + if name != "" and name[-1].isdigit(): + raise ValueError( + "Segment names are not allowed to end" + f" in a number. {name} is " + "therefore not a valid name." + ) + + self._funlist = funlist + + # Make special functions live in the funlist but transfer their names + # to the namelist + # Infer names from signature if not given, i.e. allow for '' names + for ii, name in enumerate(namelist): + if isinstance(funlist[ii], str): + namelist[ii] = funlist[ii] + elif name == "": + namelist[ii] = funlist[ii].__name__ + + # Allow single arguments to be given as not tuples + for ii, args in enumerate(argslist): + if not isinstance(args, tuple): + argslist[ii] = (args,) + self._argslist = argslist + + self._namelist = namelist + namelist = self._make_names_unique(namelist) + + # initialise markers + if marker1 is None: + self.marker1 = [] + else: + self.marker1 = marker1 + if marker2 is None: + self.marker2 = [] + else: + self.marker2 = marker2 + if segmentmarker1 is None: + self._segmark1 = [(0, 0)] * len(funlist) + else: + self._segmark1 = segmentmarker1 + if segmentmarker2 is None: + self._segmark2 = [(0, 0)] * len(funlist) + else: + self._segmark2 = segmentmarker2 + + if durslist is not None: + self._durslist = list(durslist) + else: + self._durslist = None + + self._SR = SR + + @staticmethod + def _basename(string): + """ + Remove trailing numbers from a string. + """ + + if not isinstance(string, str): + raise ValueError( + f"_basename received a non-string input! Got the following: {string}" + ) + + if string == "": + return string + if not (string[-1].isdigit()): + return string + else: + counter = 0 + for ss in string[::-1]: + if ss.isdigit(): + counter += 1 + else: + break + return string[:-counter] + + # lst = [letter for letter in string if not letter.isdigit()] + # return ''.join(lst) + + @staticmethod + def _make_names_unique(lst): + """ + Make all strings in the input list unique + by appending numbers to reoccuring strings + + Args: + lst (list): List of strings. Intended for the _namelist + + """ + + if not isinstance(lst, list): + raise ValueError(f"_make_names_unique received a non-list input! Got {lst}") + + baselst = [BluePrint._basename(lstel) for lstel in lst] + uns = np.unique(baselst) + + for un in uns: + inds = [ii for ii, el in enumerate(baselst) if el == un] + for ii, ind in enumerate(inds): + # Do not append numbers to the first occurence + if ii == 0: + lst[ind] = f"{un}" + else: + lst[ind] = f"{un}{ii + 1}" + + return lst + + @property + def length_segments(self): + """ + Returns the number of segments in the blueprint + """ + return len(self._namelist) + + @property + def duration(self): + """ + The total duration of the BluePrint. If necessary, all the arrays + are built. + """ + waits = "waituntil" in self._funlist + ensavgs = "ensureaverage_fixed_level" in self._funlist + + if not (waits) and not (ensavgs): + return sum(self._durslist) + elif waits and not (ensavgs): + waitdurations = self._makeWaitDurations() + return sum(waitdurations) + elif ensavgs: + # TODO: call the forger + raise NotImplementedError( + "ensureaverage_fixed_level does not exist yet. Cannot proceed" + ) + + @property + def points(self): + """ + The total number of points in the BluePrint. If necessary, + all the arrays are built. + """ + waits = "waituntil" in self._funlist + ensavgs = "ensureaverage_fixed_level" in self._funlist + SR = self.SR + + if SR is None: + raise ValueError( + "No sample rate specified, can not return the number of points." + ) + + if not (waits) and not (ensavgs): + return int(np.round(sum(self._durslist) * SR)) + elif waits and not (ensavgs): + waitdurations = self._makeWaitDurations() + return int(np.round(sum(waitdurations) * SR)) + elif ensavgs: + # TODO: call the forger + raise NotImplementedError( + "ensureaverage_fixed_level does not exist yet. Cannot proceed" + ) + + @property + def durations(self): + """ + The list of durations + """ + return self._durslist + + @property + def SR(self): + """ + Sample rate of the blueprint + """ + return self._SR + + @property + def description(self): + """ + Returns a dict describing the blueprint. + """ + desc = {} # the dict to return + + no_segs = len(self._namelist) + + for sn in range(no_segs): + segkey = f"segment_{sn + 1:02d}" + desc[segkey] = {} + desc[segkey]["name"] = self._namelist[sn] + if self._funlist[sn] == "waituntil": + desc[segkey]["function"] = self._funlist[sn] + else: + funname = str(self._funlist[sn])[1:] + funname = funname[: funname.find(" at")] + desc[segkey]["function"] = funname + desc[segkey]["durations"] = self._durslist[sn] + if desc[segkey]["function"] == "waituntil": + desc[segkey]["arguments"] = {"waittime": self._argslist[sn]} + else: + sig = signature(self._funlist[sn]) + desc[segkey]["arguments"] = dict( + zip(sig.parameters, self._argslist[sn]) + ) + + desc["marker1_abs"] = self.marker1 + desc["marker2_abs"] = self.marker2 + desc["marker1_rel"] = self._segmark1 + desc["marker2_rel"] = self._segmark2 + + return desc + +
+[docs] + def write_to_json(self, path_to_file: str) -> None: + """ + Writes blueprint to JSON file + + Args: + path_to_file: the path to the file to write to ex: + path_to_file/blueprint.json + """ + with open(path_to_file, "w") as fp: + json.dump(self.description, fp, indent=4)
+ + +
+[docs] + @classmethod + def blueprint_from_description(cls, blue_dict): + """ + Returns a blueprint from a description given as a dict + + Args: + blue_dict: a dict in the same form as returned by + BluePrint.description + """ + knowfunctions = { + f"function PulseAtoms.{fun}": getattr(PulseAtoms, fun) + for fun in dir(PulseAtoms) + if "__" not in fun + } + seg_mar_list = list(blue_dict.keys()) + seg_list = [s for s in seg_mar_list if "segment" in s] + bp_sum = cls() + for i, seg in enumerate(seg_list): + seg_dict = blue_dict[seg] + bp_seg = BluePrint() + if seg_dict["function"] == "waituntil": + arguments = blue_dict[seg]["arguments"].values() + arguments = (list(arguments)[0][0],) + bp_seg.insertSegment(i, "waituntil", arguments) + else: + arguments = tuple(blue_dict[seg]["arguments"].values()) + bp_seg.insertSegment( + i, + knowfunctions[seg_dict["function"]], + arguments, + name=re.sub(r"\d", "", seg_dict["name"]), + dur=seg_dict["durations"], + ) + bp_sum = bp_sum + bp_seg + bp_sum.marker1 = blue_dict["marker1_abs"] + bp_sum.marker2 = blue_dict["marker2_abs"] + listmarker1 = blue_dict["marker1_rel"] + listmarker2 = blue_dict["marker2_rel"] + bp_sum._segmark1 = [tuple(mark) for mark in listmarker1] + bp_sum._segmark2 = [tuple(mark) for mark in listmarker2] + return bp_sum
+ + +
+[docs] + @classmethod + def init_from_json(cls, path_to_file: str) -> "BluePrint": + """ + Reads blueprint from JSON file + + Args: + path_to_file: the path to the file to be read ex: + path_to_file/blueprint.json + This function is the inverse of write_to_json + The JSON file needs to be structured as if it was writen + by the function write_to_json + """ + with open(path_to_file) as fp: + data_loaded = json.load(fp) + return cls.blueprint_from_description(data_loaded)
+ + + def _makeWaitDurations(self): + """ + Translate waituntills into durations and return that list. + """ + + if "ensureaverage_fixed_level" in self._funlist: + raise NotImplementedError( + 'There is an "ensureaverage_fixed_level"' + " in this BluePrint. Cannot compute." + ) + + funlist = self._funlist.copy() + durations = self._durslist.copy() + argslist = self._argslist + + no_of_waits = funlist.count("waituntil") + + waitpositions = [ii for ii, el in enumerate(funlist) if el == "waituntil"] + + # Calculate elapsed times + + for nw in range(no_of_waits): + pos = waitpositions[nw] + funlist[pos] = PulseAtoms.waituntil + elapsed_time = sum(durations[:pos]) + wait_time = argslist[pos][0] + dur = wait_time - elapsed_time + if dur < 0: + raise ValueError( + "Inconsistent timing. Can not wait until " + f"{wait_time} at position {pos}." + f" {elapsed_time} elapsed already" + ) + else: + durations[pos] = dur + + return durations + +
+[docs] + def showPrint(self): + """ + Pretty-print the contents of the BluePrint. Not finished. + """ + # TODO: tidy up this method and make it use the description property + + if self._durslist is None: + dl = [None] * len(self._namelist) + else: + dl = self._durslist + + datalists = [self._namelist, self._funlist, self._argslist, dl] + + lzip = zip(*datalists) + + print("Legend: Name, function, arguments, timesteps, durations") + + for ind, (name, fun, args, dur) in enumerate(lzip): + ind_p = ind + 1 + if fun == "waituntil": + fun_p = fun + else: + fun_p = fun.__str__().split(" ")[1] + + list_p = [ind_p, name, fun_p, args, dur] + print('Segment {}: "{}", {}, {}, {}'.format(*list_p)) + print("-" * 10)
+ + +
+[docs] + def changeArg(self, name, arg, value, replaceeverywhere=False): + """ + Change an argument of one or more of the functions in the blueprint. + + Args: + name (str): The name of the segment in which to change an argument + arg (Union[int, str]): Either the position (int) or name (str) of + the argument to change + value (Union[int, float]): The new value of the argument + replaceeverywhere (bool): If True, the same argument is overwritten + in ALL segments where the name matches. E.g. 'gaussian1' will + match 'gaussian', 'gaussian2', etc. If False, only the segment + with exact name match gets a replacement. + + Raises: + ValueError: If the argument can not be matched (either the argument + name does not match or the argument number is wrong). + ValueError: If the name can not be matched. + + """ + # TODO: is there any reason to use tuples internally? + + if replaceeverywhere: + basename = BluePrint._basename + name = basename(name) + nmlst = self._namelist + replacelist = [nm for nm in nmlst if basename(nm) == name] + else: + replacelist = [name] + + # Validation + if name not in self._namelist: + raise ValueError( + "No segment of that name in blueprint." + f" Contains segments: {self._namelist}" + ) + + for name in replacelist: + position = self._namelist.index(name) + function = self._funlist[position] + sig = signature(function) + + # Validation + if isinstance(arg, str): + if arg not in sig.parameters: + raise ValueError( + "No such argument of function " + f"{function.__name__}. Has arguments " + f"{sig.parameters.keys()}." + ) + # Each function has two 'secret' arguments, SR and dur + user_params = len(sig.parameters) - 2 + if isinstance(arg, int) and (arg not in range(user_params)): + raise ValueError( + f"No argument {arg} " + f"of function {function.__name__}." + f" Has {user_params} " + "arguments." + ) + + # allow the user to input single values instead of (val,) + no_of_args = len(self._argslist[position]) + if not isinstance(value, tuple) and no_of_args == 1: + value = (value,) + + if isinstance(arg, str): + for ii, param in enumerate(sig.parameters): + if arg == param: + arg = ii + break + + # Mutating the immutable... + larg = list(self._argslist[position]) + larg[arg] = value + self._argslist[position] = tuple(larg)
+ + +
+[docs] + def changeDuration(self, name, dur, replaceeverywhere=False): + """ + Change the duration of one or more segments in the blueprint + + Args: + name (str): The name of the segment in which to change duration + dur (Union[float, int]): The new duration. + replaceeverywhere (Optional[bool]): If True, the duration(s) + is(are) overwritten in ALL segments where the name matches. + E.g. 'gaussian1' will match 'gaussian', 'gaussian2', + etc. If False, only the segment with exact name match + gets a replacement. + + Raises: + ValueError: If durations are not specified for the blueprint + ValueError: If too many or too few durations are given. + ValueError: If no segment matches the name. + ValueError: If dur is not positive + ValueError: If SR is given for the blueprint and dur is less than + 1/SR. + """ + + if not (isinstance(dur, float)) and not (isinstance(dur, int)): + raise ValueError( + f"New duration must be an int or a float. Received {type(dur)}" + ) + + if replaceeverywhere: + basename = BluePrint._basename + name = basename(name) + nmlst = self._namelist + replacelist = [nm for nm in nmlst if basename(nm) == name] + else: + replacelist = [name] + + # Validation + if name not in self._namelist: + raise ValueError( + "No segment of that name in blueprint." + f" Contains segments: {self._namelist}" + ) + + for name in replacelist: + position = self._namelist.index(name) + + if dur <= 0: + raise ValueError("Duration must be strictly greater than zero.") + + if self.SR is not None: + if dur * self.SR < 1: + raise ValueError( + "Duration too short! Must be at least 1/sample rate." + ) + + self._durslist[position] = dur
+ + +
+[docs] + def setSR(self, SR): + """ + Set the associated sample rate + + Args: + SR (Union[int, float]): The sample rate in Sa/s. + """ + self._SR = SR
+ + +
+[docs] + def setSegmentMarker(self, name, specs, markerID): + """ + Bind a marker to a specific segment. + + Args: + name (str): Name of the segment + specs (tuple): Marker specification tuple, (delay, duration), + where the delay is relative to the segment start + markerID (int): Which marker channel to output on. Must be 1 or 2. + """ + if markerID not in [1, 2]: + raise ValueError(f"MarkerID must be either 1 or 2. Received {markerID}.") + + markerselect = {1: self._segmark1, 2: self._segmark2} + position = self._namelist.index(name) + + # TODO: Do we need more than one bound marker per segment? + markerselect[markerID][position] = specs
+ + +
+[docs] + def removeSegmentMarker(self, name: str, markerID: int) -> None: + """ + Remove all bound markers from a specific segment + + Args: + name (str): Name of the segment + markerID (int): Which marker channel to remove from (1 or 2). + number (int): The number of the marker, in case several markers are + bound to one element. Default: 1 (the first marker). + """ + if markerID not in [1, 2]: + raise ValueError("MarkerID must be either 1 or 2. Received {markerID}.") + + markerselect = {1: self._segmark1, 2: self._segmark2} + try: + position = self._namelist.index(name) + except ValueError: + raise KeyError(f"No segment named {name} in this BluePrint.") + markerselect[markerID][position] = (0, 0)
+ + +
+[docs] + def copy(self): + """ + Returns a copy of the BluePrint + """ + + # Needed because of input validation in __init__ + namelist = [self._basename(name) for name in self._namelist.copy()] + + return BluePrint( + self._funlist.copy(), + self._argslist.copy(), + namelist, + self.marker1.copy(), + self.marker2.copy(), + self._segmark1.copy(), + self._segmark2.copy(), + self._SR, + self._durslist, + )
+ + +
+[docs] + def insertSegment(self, pos, func, args=(), dur=None, name=None, durs=None): + """ + Insert a segment into the bluePrint. + + Args: + pos (int): The position at which to add the segment. Counts like + a python list; 0 is first, -1 is last. Values below -1 are + not allowed, though. + func (function): Function describing the segment. Must have its + duration as the last argument (unless its a special function). + args (Optional[Tuple[Any]]): Tuple of arguments BESIDES duration. + Default: () + dur (Optional[Union[int, float]]): The duration of the + segment. Must be given UNLESS the segment is + 'waituntil' or 'ensureaverage_fixed_level' + name Optional[str]: Name of the segment. If none is given, + the segment will receive the name of its function, + possibly with a number appended. + + Raises: + ValueError: If the position is negative + ValueError: If the name ends in a number + """ + + # Validation + has_ensureavg = ( + "ensureaverage_fixed_level" in self._funlist + or "ensureaverage_fixed_dur" in self._funlist + ) + if func == "ensureaverage_fixed_level" and has_ensureavg: + raise ValueError( + 'Can not have more than one "ensureaverage" segment in a blueprint.' + ) + + if durs is not None: + warnings.warn( + 'Deprecation warning: please specify "dur" rather ' + 'than "durs" when inserting a segment' + ) + if dur is None: + dur = durs + else: + raise ValueError('You can not specify "durs" AND "dur"!') + # Take care of 'waituntil' + + # allow users to input single values + if not isinstance(args, tuple): + args = (args,) + + if pos < -1: + raise ValueError("Position must be strictly larger than -1") + + if name is None or name == "": + if func == "waituntil": + name = "waituntil" + else: + name = func.__name__ + elif isinstance(name, str): + if len(name) > 0: + if name[-1].isdigit(): + raise ValueError("Segment name must not end in a number") + + if pos == -1: + self._namelist.append(name) + self._namelist = self._make_names_unique(self._namelist) + self._funlist.append(func) + self._argslist.append(args) + self._segmark1.append((0, 0)) + self._segmark2.append((0, 0)) + self._durslist.append(dur) + else: + self._namelist.insert(pos, name) + self._namelist = self._make_names_unique(self._namelist) + self._funlist.insert(pos, func) + self._argslist.insert(pos, args) + self._segmark1.insert(pos, (0, 0)) + self._segmark2.insert(pos, (0, 0)) + self._durslist.insert(pos, dur)
+ + +
+[docs] + def removeSegment(self, name): + """ + Remove the specified segment from the blueprint. + + Args: + name (str): The name of the segment to remove. + """ + try: + position = self._namelist.index(name) + except ValueError: + raise KeyError(f"No segment called {name} in blueprint.") + + del self._funlist[position] + del self._argslist[position] + del self._namelist[position] + del self._segmark1[position] + del self._segmark2[position] + del self._durslist[position] + + self._namelist = self._make_names_unique(self._namelist)
+ + + def __add__(self, other): + """ + Add two BluePrints. The second argument is appended to the first + and a new BluePrint is returned. + + Args: + other (BluePrint): A BluePrint instance + + Returns: + BluePrint: A new blueprint. + + Raises: + ValueError: If the input is not a BluePrint instance + """ + if not isinstance(other, BluePrint): + raise ValueError( + f""" + BluePrint can only be added to another Blueprint. + Received an object of type {type(other)} + """ + ) + + nl = [self._basename(name) for name in self._namelist] + nl += [self._basename(name) for name in other._namelist] + al = self._argslist + other._argslist + fl = self._funlist + other._funlist + m1 = self.marker1 + other.marker1 + m2 = self.marker2 + other.marker2 + sm1 = self._segmark1 + other._segmark1 + sm2 = self._segmark2 + other._segmark2 + dl = self._durslist + other._durslist + + new_bp = BluePrint() + + new_bp._namelist = new_bp._make_names_unique(nl.copy()) + new_bp._funlist = fl.copy() + new_bp._argslist = al.copy() + new_bp.marker1 = m1.copy() + new_bp.marker2 = m2.copy() + new_bp._segmark1 = sm1.copy() + new_bp._segmark2 = sm2.copy() + new_bp._durslist = dl.copy() + + if self.SR is not None: + new_bp.setSR(self.SR) + + return new_bp + + def __eq__(self, other): + """ + Compare two blueprints. They are the same iff all + lists are identical. + + Args: + other (BluePrint): A BluePrint instance + + Returns: + bool: whether the two blueprints are identical + + Raises: + ValueError: If the input is not a BluePrint instance + """ + if not isinstance(other, BluePrint): + raise ValueError( + f""" + Blueprint can only be compared to another + Blueprint. + Received an object of type {type(other)} + """ + ) + + if not self._namelist == other._namelist: + return False + if not self._funlist == other._funlist: + return False + if not self._argslist == other._argslist: + return False + if not self.marker1 == other.marker1: + return False + if not self.marker2 == other.marker2: + return False + if not self._segmark1 == other._segmark1: + return False + if not self._segmark2 == other._segmark2: + return False + return True
+ + + +def _subelementBuilder( + blueprint: BluePrint, SR: int, durs: list[float] +) -> dict[str, np.ndarray]: + """ + The function building a blueprint, returning a numpy array. + + This is the core translater from description of pulse to actual data points + All arrays must be made with this function + """ + + # Important: building the element must NOT modify any of the mutable + # inputs, therefore all lists are copied + funlist = blueprint._funlist.copy() + argslist = blueprint._argslist.copy() + namelist = blueprint._namelist.copy() + marker1 = blueprint.marker1.copy() + marker2 = blueprint.marker2.copy() + segmark1 = blueprint._segmark1.copy() + segmark2 = blueprint._segmark2.copy() + + durations = durs.copy() + + no_of_waits = funlist.count("waituntil") + + # handle waituntil by translating it into a normal function + waitpositions = [ii for ii, el in enumerate(funlist) if el == "waituntil"] + + # Calculate elapsed times + + for nw in range(no_of_waits): + pos = waitpositions[nw] + funlist[pos] = PulseAtoms.waituntil + elapsed_time = sum(durations[:pos]) + wait_time = argslist[pos][0] + dur = wait_time - elapsed_time + if dur < 0: + raise ValueError( + "Inconsistent timing. Can not wait until " + f"{wait_time} at position {pos}." + f" {elapsed_time} elapsed already" + ) + else: + durations[pos] = dur + + # When special segments like 'waituntil' and 'ensureaverage' get + # evaluated, the list of durations gets updated. That new list + # is newdurations + + newdurations = np.array(durations) + + # All waveforms must ultimately have an integer number of samples + # Now figure out from the durations what these integers are + # + # The most honest thing to do is to simply round off dur*SR + # and raise an exception if the segment ends up with less than + # two points + + intdurations = np.zeros(len(newdurations), dtype=int) + + for ii, dur in enumerate(newdurations): + int_dur = round(dur * SR) + if int_dur < 2: + raise SegmentDurationError( + "Too short segment detected! " + f'Segment "{namelist[ii]}" at position {ii} ' + f"has a duration of {newdurations[ii]} which at " + f"an SR of {SR:.3E} leads to just {int_dur} " + "point(s). There must be at least " + "2 points in each segment." + "" + ) + else: + intdurations[ii] = int_dur + newdurations[ii] = int_dur / SR + + # The actual forging of the waveform + wf_length = np.sum(intdurations) + parts = [ft.partial(fun, *args) for (fun, args) in zip(funlist, argslist)] + blocks = [p(SR, d) for (p, d) in zip(parts, intdurations)] + output = np.fromiter( + (block for sl in blocks for block in sl), float, count=wf_length + ) + + # now make the markers + time = np.linspace(0, sum(newdurations), wf_length, endpoint=False) + m1 = np.zeros_like(time) + m2 = np.zeros_like(time) + + # update the 'absolute time' marker list with 'relative time' + # (segment bound) markers converted to absolute time + elapsed_times = np.cumsum([0.0] + list(newdurations)) + + for pos, spec in enumerate(segmark1): + if spec[1] != 0: + ontime = elapsed_times[pos] + spec[0] # spec is (delay, duration) + marker1.append((ontime, spec[1])) + for pos, spec in enumerate(segmark2): + if spec[1] != 0: + ontime = elapsed_times[pos] + spec[0] # spec is (delay, duration) + marker2.append((ontime, spec[1])) + msettings = [marker1, marker2] + marks = [m1, m2] + for marker, setting in zip(marks, msettings): + for t, dur in setting: + ind = np.abs(time - t).argmin() + chunk = int(np.round(dur * SR)) + marker[ind : ind + chunk] = 1 + + outdict = { + "wfm": output, + "m1": m1, + "m2": m2, + "time": time, + "newdurations": newdurations, + } + + return outdict +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/broadbean.html b/_modules/broadbean/broadbean.html new file mode 100644 index 000000000..92dd87921 --- /dev/null +++ b/_modules/broadbean/broadbean.html @@ -0,0 +1,530 @@ + + + + + + + + broadbean.broadbean - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.broadbean

+import functools as ft
+import logging
+import warnings
+from collections.abc import Callable
+
+import numpy as np
+
+log = logging.getLogger(__name__)
+
+
+
+[docs] +class PulseAtoms: + """ + A class full of static methods. + The basic pulse shapes. + + Any pulse shape function should return a list or an np.array + and have SR, npoints as its final two arguments. + + Rounding errors are a real concern/pain in the business of + making waveforms of short duration (few samples). Therefore, + the PulseAtoms take the number of points rather than the + duration as input argument, so that all ambiguity can be handled + in one place (the _subelementBuilder) + """ + +
+[docs] + @staticmethod + def sine(freq, ampl, off, phase, SR, npts): + time = np.linspace(0, npts / SR, int(npts), endpoint=False) + freq *= 2 * np.pi + return ampl * np.sin(freq * time + phase) + off
+ + +
+[docs] + @staticmethod + def ramp(start, stop, SR, npts): + dur = npts / SR + slope = (stop - start) / dur + time = np.linspace(0, dur, int(npts), endpoint=False) + return slope * time + start
+ + +
+[docs] + @staticmethod + def arb_func(func: Callable, kwargs, SR, npts): + r""" + This function is used to generate an arbitrary waveform from a function. + The function must be of the form f(time, \*\*kwargs) where time is a numpy array and + kwargs is a dict that provides any additional parameters needed for the function. + + Example: + + .. code-block:: python + + func = lambda time, freq, ampl, off, phase: ampl*np.sin(freq*time+phase)+off + kwargs = {'freq': 1e6, 'ampl': 1, 'off': 0, 'phase': 0} + + """ + time = np.linspace(0, npts / SR, int(npts), endpoint=False) + return func(time, **kwargs)
+ + +
+[docs] + @staticmethod + def waituntil(dummy, SR, npts): + # for internal call signature consistency, a dummy variable is needed + return np.zeros(int(npts))
+ + +
+[docs] + @staticmethod + def gaussian(ampl, sigma, mu, offset, SR, npts): + """ + Returns a Gaussian of peak height ampl (when offset==0) + + Is by default centred in the middle of the interval + """ + dur = npts / SR + time = np.linspace(0, dur, int(npts), endpoint=False) + centre = dur / 2 + baregauss = np.exp(-((time - mu - centre) ** 2) / (2 * sigma**2)) + return ampl * baregauss + offset
+ + +
+[docs] + @staticmethod + def gaussian_smooth_cutoff(ampl, sigma, mu, offset, SR, npts): + """ + Returns a Gaussian of peak height ampl (when offset==0) + + Is by default centred in the middle of the interval + + smooth cutoff by making offsetting the Gaussian so endpoint = 0 and normalizing the hight to 1 + """ + dur = npts / SR + time = np.linspace(0, dur, int(npts), endpoint=False) + centre = dur / 2 + baregauss = np.exp(-((time - mu - centre) ** 2) / (2 * sigma**2)) - np.exp( + -((0 - mu - centre) ** 2) / (2 * sigma**2) + ) + normalization = 1 / (1.0 - np.exp(-((0 - mu - centre) ** 2) / (2 * sigma**2))) + return ampl * baregauss / normalization + offset
+
+ + + +
+[docs] +def marked_for_deletion(replaced_by: str | None = None) -> Callable: + """ + A decorator for functions we want to kill. The function still + gets called. + """ + + def decorator(func): + @ft.wraps(func) + def warner(*args, **kwargs): + warnstr = f"{func.__name__} is obsolete." + if replaced_by: + warnstr += f" Please use {replaced_by} insted." + warnings.warn(warnstr) + return func(*args, **kwargs) + + return warner + + return decorator
+ + + +def _channelListSorter(channels: list[str | int]) -> list[str | int]: + """ + Sort a list of channel names. Channel names can be ints or strings. Sorts + ints as being before strings. + """ + intlist: list[str | int] = [] + intlist = [ch for ch in channels if isinstance(ch, int)] + strlist: list[str | int] = [] + strlist = [ch for ch in channels if isinstance(ch, str)] + + sorted_list = sorted(intlist) + sorted(strlist) + + return sorted_list + + +class _AWGOutput: + """ + Class used inside Sequence.outputForAWGFile + + Allows for easy-access slicing to return several valid tuples + for the QCoDeS Tektronix AWG 5014 driver from the same sequence. + + Example: + A sequence, myseq, specifies channels 1, 2, 3, 4. + + out = myseq.outputForAWGFile() + + out[:] <--- tuple with all channels + out[1:3] <--- tuple with channels 1, 2 + out[2] <--- tuple with channel 2 + """ + + def __init__(self, rawpackage, channels): + """ + Rawpackage is a tuple: + (wfms, m1s, m2s, nreps, trig_wait, goto, jump) + + Channels is a list of what the channels were called in their + sequence object whence this instance is created + """ + + self.channels = channels + + self._channels = {} + for ii in range(len(rawpackage[0])): + self._channels[ii] = { + "wfms": rawpackage[0][ii], + "m1s": rawpackage[1][ii], + "m2s": rawpackage[2][ii], + } + self.nreps = rawpackage[3] + self.trig_wait = rawpackage[4] + self.goto = rawpackage[5] + self.jump = rawpackage[6] + + def __getitem__(self, key): + if isinstance(key, int): + if key in self._channels.keys(): + output = ( + [self._channels[key]["wfms"]], + [self._channels[key]["m1s"]], + [self._channels[key]["m2s"]], + self.nreps, + self.trig_wait, + self.goto, + self.jump, + ) + + return output + else: + raise KeyError(f"{key} is not a valid key.") + + if isinstance(key, slice): + start = key.start + if start is None: + start = 0 + + stop = key.stop + if stop is None: + stop = len(self._channels.keys()) + + step = key.step + if step is None: + step = 1 + + indeces = range(start, stop, step) + + wfms = [self._channels[ind]["wfms"] for ind in indeces] + m1s = [self._channels[ind]["m1s"] for ind in indeces] + m2s = [self._channels[ind]["m2s"] for ind in indeces] + + output = (wfms, m1s, m2s, self.nreps, self.trig_wait, self.goto, self.jump) + + return output + + raise KeyError("Key must be int or slice!") +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/element.html b/_modules/broadbean/element.html new file mode 100644 index 000000000..cfea72e2b --- /dev/null +++ b/_modules/broadbean/element.html @@ -0,0 +1,848 @@ + + + + + + + + broadbean.element - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.element

+# This file contains the Element definition
+from __future__ import annotations
+
+import json
+from collections.abc import Sequence
+from copy import deepcopy
+
+import numpy as np
+
+from broadbean.blueprint import BluePrint, _subelementBuilder
+
+from .broadbean import PulseAtoms
+
+
+
+[docs] +class ElementDurationError(Exception): + pass
+ + + +
+[docs] +class Element: + """ + Object representing an element. An element is a collection of waves that + are to be run simultaneously. The element consists of a number of channels + that are then each filled with anything of the appropriate length. + """ + + def __init__(self): + # The internal data structure, a dict with key channel number + # Each value is a dict with the following possible keys, values: + # 'blueprint': a BluePrint + # 'channelname': channel name for later use with a Tektronix AWG5014 + # 'array': a dict {'wfm': np.array} (other keys: 'm1', 'm2', etc) + # 'SR': Sample rate. Used with array. + # + # Another dict is meta, which holds: + # 'duration': duration in seconds of the entire element. + # 'SR': sample rate of the element + # These two values are added/updated upon validation of the durations + + self._data = {} + self._meta = {} + +
+[docs] + def addBluePrint(self, channel: str | int, blueprint: BluePrint) -> None: + """ + Add a blueprint to the element on the specified channel. + Overwrites whatever was there before. + """ + if not isinstance(blueprint, BluePrint): + raise ValueError( + "Invalid blueprint given. Must be an instance of the BluePrint class." + ) + + if [] in [ + blueprint._funlist, + blueprint._argslist, + blueprint._namelist, + blueprint._durslist, + ]: + raise ValueError("Received empty BluePrint. Can not proceed.") + + # important: make a copy of the blueprint + newprint = blueprint.copy() + + self._data[channel] = {} + self._data[channel]["blueprint"] = newprint
+ + +
+[docs] + def addFlags(self, channel: str | int, flags: Sequence[str | int]) -> None: + """ + Adds flags for the specified channel. + List of 4 flags, each of which should be 0 or "" for 'No change', 1 or "H" for 'High', + 2 or "L" for 'Low', 3 or "T" for 'Toggle', 4 or "P" for 'Pulse'. + """ + if not isinstance(flags, Sequence): + raise ValueError( + "Flags should be given as a sequence (e.g. a list or a tuple)." + ) + + if len(flags) != 4: + raise ValueError("There should be 4 flags in the list.") + + for cnt, i in enumerate(flags): + if i not in [0, 1, 2, 3, 4, "", "H", "L", "T", "P"]: + raise ValueError( + 'Invalid flag at index {cnt}. Allowed flags are 0 or "" (No change), ' + '1 or "H" (High), 2 or "L" (Low), 3 or "T" (Toggle), ' + '4 or "P" (Pulse).' + ) + + # replace flag aliases with integers + flag_aliases = { + "": 0, + "H": 1, + "L": 2, + "T": 3, + "P": 4, + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + } + flags_int = [flag_aliases[x] for x in flags] + + self._data[channel]["flags"] = flags_int
+ + +
+[docs] + def addArray( + self, channel: int | str, waveform: np.ndarray, SR: int, **kwargs + ) -> None: + """ + Add an array of voltage value to the element on the specified channel. + Overwrites whatever was there before. Markers can be specified via + the kwargs, i.e. the kwargs must specify arrays of markers. The names + can be 'm1', 'm2', 'm3', etc. + + Args: + channel: The channel number + waveform: The array of waveform values (V) + SR: The sample rate in Sa/s + """ + + N = len(waveform) + self._data[channel] = {} + self._data[channel]["array"] = {} + + for name, array in kwargs.items(): + if len(array) != N: + raise ValueError( + "Length mismatch between waveform and " + f"array {name}. Must be same length" + ) + self._data[channel]["array"].update({name: array}) + + self._data[channel]["array"]["wfm"] = waveform + self._data[channel]["SR"] = SR
+ + +
+[docs] + def validateDurations(self): + """ + Check that all channels have the same specified duration, number of + points and sample rate. + """ + + # pick out the channel entries + channels = self._data.values() + + if len(channels) == 0: + raise KeyError("Empty Element, nothing assigned") + + # First the sample rate + SRs = [] + for channel in channels: + if "blueprint" in channel.keys(): + SRs.append(channel["blueprint"].SR) + elif "array" in channel.keys(): + SR = channel["SR"] + SRs.append(SR) + + if not SRs.count(SRs[0]) == len(SRs): + errmssglst = zip(list(self._data.keys()), SRs) + raise ElementDurationError( + "Different channels have different " + "SRs. (Channel, SR): " + f"{list(errmssglst)}" + ) + + # Next the total time + durations = [] + for channel in channels: + if "blueprint" in channel.keys(): + durations.append(channel["blueprint"].duration) + elif "array" in channel.keys(): + length = len(channel["array"]["wfm"]) / channel["SR"] + durations.append(length) + + if None not in SRs: + atol = min(SRs) + else: + atol = 1e-9 + + if not np.allclose(durations, durations[0], atol=atol): + errmssglst = zip(list(self._data.keys()), durations) + raise ElementDurationError( + "Different channels have different " + "durations. (Channel, duration): " + f"{list(errmssglst)}s" + ) + + # Finally the number of points + # (kind of redundant if sample rate and duration match?) + npts = [] + for channel in channels: + if "blueprint" in channel.keys(): + npts.append(channel["blueprint"].points) + elif "array" in channel.keys(): + length = len(channel["array"]["wfm"]) + npts.append(length) + + if not npts.count(npts[0]) == len(npts): + errmssglst = zip(list(self._data.keys()), npts) + raise ElementDurationError( + "Different channels have different " + "npts. (Channel, npts): " + f"{list(errmssglst)}" + ) + + # If these three tests pass, we equip the dictionary with convenient + # info used by Sequence + self._meta["SR"] = SRs[0] + self._meta["duration"] = durations[0]
+ + +
+[docs] + def getArrays(self, includetime: bool = False) -> dict[int, dict[str, np.ndarray]]: + """ + Return arrays of the element. Heavily used by the Sequence. + + Args: + includetime: Whether to include time arrays. They will have the key + 'time'. Time should be included when plotting, otherwise not. + + Returns: + dict: + Dictionary with channel numbers (ints) as keys and forged + blueprints as values. A forged blueprint is a dict with + the mandatory key 'wfm' and optional keys 'm1', 'm2', 'm3' (etc) + and 'time'. + + """ + + outdict = {} + for channel, signal in self._data.items(): + if "array" in signal.keys(): + outdict[channel] = signal["array"] + if includetime and "time" not in signal["array"].keys(): + N = len(signal["array"]["wfm"]) + dur = N / signal["SR"] + outdict[channel]["time"] = np.linspace(0, dur, N) + elif "blueprint" in signal.keys(): + bp = signal["blueprint"] + durs = bp.durations + SR = bp.SR + forged_bp = _subelementBuilder(bp, SR, durs) + outdict[channel] = forged_bp + if "flags" in signal.keys(): + outdict[channel]["flags"] = signal["flags"] + if not includetime: + outdict[channel].pop("time") + outdict[channel].pop("newdurations") + # TODO: should the be a separate bool for newdurations? + + return outdict
+ + + @property + def SR(self): + """ + Returns the sample rate, if well-defined. Else raises + an error about what went wrong. + """ + # Will either raise an error or set self._data['SR'] + self.validateDurations() + + return self._meta["SR"] + + @property + def points(self) -> int: + """ + Returns the number of points of each channel if that number is + well-defined. Else an error is raised. + """ + self.validateDurations() + + # pick out what is on the channels + channels = self._data.values() + + # if validateDurations did not raise an error, all channels + # have the same number of points + for chan in channels: + if not ("array" in chan.keys() or "blueprint" in chan.keys()): + raise ValueError( + f"Neither BluePrint nor array assigned to chan {chan}!" + ) + if "blueprint" in chan.keys(): + return chan["blueprint"].points + else: + return len(chan["array"]["wfm"]) + + else: + # this line is here to make mypy happy; this exception is + # already raised by validateDurations + raise KeyError("Empty Element, nothing assigned") + + @property + def duration(self): + """ + Returns the duration in seconds of the element, if said duration is + well-defined. Else raises an error. + """ + # Will either raise an error or set self._data['SR'] + self.validateDurations() + + return self._meta["duration"] + + @property + def channels(self): + """ + The channels that has something on them + """ + chans = [key for key in self._data.keys()] + return chans + + @property + def description(self): + """ + Returns a dict describing the element. + """ + desc = {} + + for key, val in self._data.items(): + if "blueprint" in val.keys(): + desc[str(key)] = val["blueprint"].description + elif "array" in val.keys(): + desc[str(key)] = "array" + + if "flags" in val.keys(): + desc[str(key)]["flags"] = val["flags"] + + return desc + +
+[docs] + def write_to_json(self, path_to_file: str) -> None: + """ + Writes element to JSON file + + Args: + path_to_file: the path to the file to write to ex: + path_to_file/element.json + """ + with open(path_to_file, "w") as fp: + json.dump(self.description, fp, indent=4)
+ + +
+[docs] + @classmethod + def element_from_description(cls, element_dict): + """ + Returns a blueprint from a description given as a dict + + Args: + element_dict: a dict in the same form as returned by + Element.description + """ + channels_list = list(element_dict.keys()) + elem = cls() + for chan in channels_list: + bp_sum = BluePrint.blueprint_from_description(element_dict[chan]) + elem.addBluePrint(int(chan), bp_sum) + return elem
+ + +
+[docs] + @classmethod + def init_from_json(cls, path_to_file: str) -> Element: + """ + Reads Element from JSON file + + Args: + path_to_file: the path to the file to be read ex: + path_to_file/Element.json + This function is the inverse of write_to_json + The JSON file needs to be structured as if it was writen + by the function write_to_json + """ + with open(path_to_file) as fp: + data_loaded = json.load(fp) + return cls.element_from_description(data_loaded)
+ + +
+[docs] + def changeArg( + self, + channel: str | int, + name: str, + arg: str | int, + value: int | float, + replaceeverywhere: bool = False, + ) -> None: + """ + Change the argument of a function of the blueprint on the specified + channel. + + Args: + channel: The channel where the blueprint sits. + name: The name of the segment in which to change an argument + arg: Either the position (int) or name (str) of + the argument to change + value: The new value of the argument + replaceeverywhere: If True, the same argument is overwritten + in ALL segments where the name matches. E.g. 'gaussian1' will + match 'gaussian', 'gaussian2', etc. If False, only the segment + with exact name match gets a replacement. + + Raises: + ValueError: If the specified channel has no blueprint. + ValueError: If the argument can not be matched (either the argument + name does not match or the argument number is wrong). + """ + + if channel not in self.channels: + raise ValueError(f"Nothing assigned to channel {channel}") + + if "blueprint" not in self._data[channel].keys(): + raise ValueError(f"No blueprint on channel {channel}.") + + bp = self._data[channel]["blueprint"] + + bp.changeArg(name, arg, value, replaceeverywhere)
+ + +
+[docs] + def changeDuration( + self, + channel: str | int, + name: str, + newdur: int | float, + replaceeverywhere: bool = False, + ) -> None: + """ + Change the duration of a segment of the blueprint on the specified + channel + + Args: + channel: The channel holding the blueprint in question + name): The name of the segment to modify + newdur: The new duration. + replaceeverywhere: If True, all segments + matching the base + name given will have their duration changed. If False, only the + segment with an exact name match will have its duration + changed. Default: False. + """ + + if channel not in self.channels: + raise ValueError(f"Nothing assigned to channel {channel}") + + if "blueprint" not in self._data[channel].keys(): + raise ValueError(f"No blueprint on channel {channel}.") + + bp = self._data[channel]["blueprint"] + + bp.changeDuration(name, newdur, replaceeverywhere)
+ + + def _applyDelays(self, delays: list[float]) -> None: + """ + Apply delays to the channels of this element. This function is intended + to be used via a Sequence object. Note that this function changes + the element it is called on. Calling _applyDelays a second will apply + more delays on top of the first ones. + + Args: + delays: A list matching the channels of the Element. If there + are channels=[1, 3], then delays=[1e-3, 0] will delay channel + 1 by 1 ms and channel 3 by nothing. + """ + if len(delays) != len(self.channels): + raise ValueError( + "Incorrect number of delays specified." + " Must match the number of channels." + ) + + if not sum(d >= 0 for d in delays) == len(delays): + raise ValueError("Negative delays not allowed.") + + # The strategy is: + # Add waituntil at the beginning, update all waituntils inside, add a + # zeros segment at the end. + # If already-forged arrays are found, simply append and prepend zeros + + SR = self.SR + maxdelay = max(delays) + + for chanind, chan in enumerate(self.channels): + delay = delays[chanind] + + if "blueprint" in self._data[chan].keys(): + blueprint = self._data[chan]["blueprint"] + + # update existing waituntils + for segpos in range(len(blueprint._funlist)): + if blueprint._funlist[segpos] == "waituntil": + oldwait = blueprint._argslist[segpos][0] + blueprint._argslist[segpos] = (oldwait + delay,) + # insert delay before the waveform + if delay > 0: + blueprint.insertSegment(0, "waituntil", (delay,), "waituntil") + # add zeros at the end + if maxdelay - delay > 0: + blueprint.insertSegment( + -1, PulseAtoms.ramp, (0, 0), dur=maxdelay - delay + ) + + else: + arrays = self._data[chan]["array"] + for name, arr in arrays.items(): + pre_wait = np.zeros(int(delay * SR)) + post_wait = np.zeros(int((maxdelay - delay) * SR)) + arrays[name] = np.concatenate((pre_wait, arr, post_wait)) + +
+[docs] + def copy(self): + """ + Return a copy of the element + """ + new = Element() + new._data = deepcopy(self._data) + new._meta = deepcopy(self._meta) + return new
+ + + def __eq__(self, other): + if not isinstance(other, Element): + return False + elif not self._data == other._data: + return False + elif not self._meta == other._meta: + return False + else: + return True
+ +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/plotting.html b/_modules/broadbean/plotting.html new file mode 100644 index 000000000..7690d21e2 --- /dev/null +++ b/_modules/broadbean/plotting.html @@ -0,0 +1,659 @@ + + + + + + + + broadbean.plotting - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.plotting

+# A little helper module for plotting of broadbean objects
+
+from typing import cast
+
+import matplotlib.axes
+import matplotlib.pyplot as plt
+import numpy as np
+
+from broadbean import BluePrint, Element, Sequence
+from broadbean.sequence import SequenceConsistencyError
+
+# The object we can/want to plot
+BBObject = Sequence | BluePrint | Element
+
+
+
+[docs] +def getSIScalingAndPrefix(minmax: tuple[float, float]) -> tuple[float, str]: + """ + Return the scaling exponent and unit prefix. E.g. (-2e-3, 1e-6) will + return (1e3, 'm') + + Args: + minmax: The (min, max) value of the signal + + Returns: + A tuple of the scaling (inverse of the prefix) and the prefix + string. + + """ + v_max: float = max(map(abs, minmax)) + if v_max == 0: + v_max = 1 + exponent = np.log10(v_max) + prefix = "" + scaling: float = 1 + + if exponent < 0: + prefix = "m" + scaling = 1e3 + if exponent < -3: + prefix = "micro " + scaling = 1e6 + if exponent < -6: + prefix = "n" + scaling = 1e9 + + return (scaling, prefix)
+ + + +def _plot_object_validator(obj_to_plot: BBObject) -> None: + """ + Validate the object + """ + if isinstance(obj_to_plot, Sequence): + proceed = obj_to_plot.checkConsistency(verbose=True) + if not proceed: + raise SequenceConsistencyError + + elif isinstance(obj_to_plot, Element): + obj_to_plot.validateDurations() + + elif isinstance(obj_to_plot, BluePrint): + assert obj_to_plot.SR is not None + + +def _plot_object_forger(obj_to_plot: BBObject, **forger_kwargs) -> dict[int, dict]: + """ + Make a forged sequence out of any object. + Returns a forged sequence. + """ + + if isinstance(obj_to_plot, BluePrint): + elem = Element() + elem.addBluePrint(1, obj_to_plot) + seq = Sequence() + seq.addElement(1, elem) + seq.setSR(obj_to_plot.SR) + + elif isinstance(obj_to_plot, Element): + seq = Sequence() + seq.addElement(1, obj_to_plot) + seq.setSR(obj_to_plot._meta["SR"]) + + elif isinstance(obj_to_plot, Sequence): + seq = obj_to_plot + + forged_seq = seq.forge(includetime=True, **forger_kwargs) + + return forged_seq + + +def _plot_summariser(seq: dict[int, dict]) -> dict[int, dict[str, np.ndarray]]: + """ + Return a plotting summary of a subsequence. + + Args: + seq: The 'content' value of a forged sequence where a + subsequence resides + + Returns: + A dict that looks like a forged element, but all waveforms + are just two points, np.array([min, max]) + """ + + output = {} + + # we assume correctness, all postions specify the same channels + chans = seq[1]["data"].keys() + + minmax = dict(zip(chans, [(0, 0)] * len(chans))) + + for element in seq.values(): + arr_dict = element["data"] + + for chan in chans: + wfm = arr_dict[chan]["wfm"] + if wfm.min() < minmax[chan][0]: + minmax[chan] = (wfm.min(), minmax[chan][1]) + if wfm.max() > minmax[chan][1]: + minmax[chan] = (minmax[chan][0], wfm.max()) + output[chan] = { + "wfm": np.array(minmax[chan]), + "m1": np.zeros(2), + "m2": np.zeros(2), + "time": np.linspace(0, 1, 2), + } + + return output + + +# the Grand Unified Plotter +
+[docs] +def plotter(obj_to_plot: BBObject, **forger_kwargs) -> None: + """ + The one plot function to be called. Turns whatever it gets + into a sequence, forges it, and plots that. + """ + + # TODO: Take axes as input + + # strategy: + # * Validate + # * Forge + # * Plot + + _plot_object_validator(obj_to_plot) + + seq = _plot_object_forger(obj_to_plot, **forger_kwargs) + + # Get the dimensions. + chans = seq[1]["content"][1]["data"].keys() + seqlen = len(seq.keys()) + + def update_minmax(chanminmax, wfmdata, chanind): + (thismin, thismax) = (wfmdata.min(), wfmdata.max()) + if thismin < chanminmax[chanind][0]: + chanminmax[chanind] = [thismin, chanminmax[chanind][1]] + if thismax > chanminmax[chanind][1]: + chanminmax[chanind] = [chanminmax[chanind][0], thismax] + return chanminmax + + # Then figure out the figure scalings + minf: float = -np.inf + inf: float = np.inf + chanminmax: list[tuple[float, float]] = [(inf, minf)] * len(chans) + for chanind, chan in enumerate(chans): + for pos in range(1, seqlen + 1): + if seq[pos]["type"] == "element": + wfmdata = seq[pos]["content"][1]["data"][chan]["wfm"] + chanminmax = update_minmax(chanminmax, wfmdata, chanind) + elif seq[pos]["type"] == "subsequence": + for pos2 in seq[pos]["content"].keys(): + elem = seq[pos]["content"][pos2]["data"] + wfmdata = elem[chan]["wfm"] + chanminmax = update_minmax(chanminmax, wfmdata, chanind) + + fig, axs = plt.subplots(len(chans), seqlen, squeeze=False) + + # ...and do the plotting + for chanind, chan in enumerate(chans): + # figure out the channel voltage scaling + # The entire channel shares a y-axis + + minmax: tuple[float, float] = chanminmax[chanind] + + (voltagescaling, voltageprefix) = getSIScalingAndPrefix(minmax) + voltageunit = voltageprefix + "V" + + for pos in range(seqlen): + ax = cast(matplotlib.axes.Axes, axs[chanind, pos]) + # reduce the tickmark density (must be called before scaling) + ax.locator_params(tight=True, nbins=4, prune="lower") + + if seq[pos + 1]["type"] == "element": + content = seq[pos + 1]["content"][1]["data"][chan] + wfm = content["wfm"] + m1 = content.get("m1", np.zeros_like(wfm)) + m2 = content.get("m2", np.zeros_like(wfm)) + time = content["time"] + newdurs = content.get("newdurations", []) + + else: + arr_dict = _plot_summariser(seq[pos + 1]["content"]) + wfm = arr_dict[chan]["wfm"] + newdurs = [] + + ax.annotate( + "SUBSEQ", + xy=(0.5, 0.5), + xycoords="axes fraction", + horizontalalignment="center", + ) + time = np.linspace(0, 1, 2) # needed for timeexponent + + # Figure out the axes' scaling + timeexponent = np.log10(time.max()) + timeunit = "s" + timescaling: float = 1.0 + if timeexponent < 0: + timeunit = "ms" + timescaling = 1e3 + if timeexponent < -3: + timeunit = "micro s" + timescaling = 1e6 + if timeexponent < -6: + timeunit = "ns" + timescaling = 1e9 + + if seq[pos + 1]["type"] == "element": + ax.plot( + timescaling * time, + voltagescaling * wfm, + lw=3, + color=(0.6, 0.4, 0.3), + alpha=0.4, + ) + + ymax = voltagescaling * chanminmax[chanind][1] + ymin = voltagescaling * chanminmax[chanind][0] + yrange = ymax - ymin + ax.set_ylim((ymin - 0.05 * yrange, ymax + 0.2 * yrange)) + + if seq[pos + 1]["type"] == "element": + # TODO: make this work for more than two markers + + # marker1 (red, on top) + y_m1 = ymax + 0.15 * yrange + marker_on = np.ones_like(m1) + marker_on[m1 == 0] = np.nan + marker_off = np.ones_like(m1) + ax.plot( + timescaling * time, + y_m1 * marker_off, + color=(0.6, 0.1, 0.1), + alpha=0.2, + lw=2, + ) + ax.plot( + timescaling * time, + y_m1 * marker_on, + color=(0.6, 0.1, 0.1), + alpha=0.6, + lw=2, + ) + + # marker 2 (blue, below the red) + y_m2 = ymax + 0.10 * yrange + marker_on = np.ones_like(m2) + marker_on[m2 == 0] = np.nan + marker_off = np.ones_like(m2) + ax.plot( + timescaling * time, + y_m2 * marker_off, + color=(0.1, 0.1, 0.6), + alpha=0.2, + lw=2, + ) + ax.plot( + timescaling * time, + y_m2 * marker_on, + color=(0.1, 0.1, 0.6), + alpha=0.6, + lw=2, + ) + + # If subsequence, plot lines indicating min and max value + if seq[pos + 1]["type"] == "subsequence": + # min: + ax.plot( + time, + np.ones_like(time) * wfm[0], + color=(0.12, 0.12, 0.12), + alpha=0.2, + lw=2, + ) + # max: + ax.plot( + time, + np.ones_like(time) * wfm[1], + color=(0.12, 0.12, 0.12), + alpha=0.2, + lw=2, + ) + + ax.set_xticks([]) + + # time step lines + for dur in np.cumsum(newdurs): + ax.plot( + [timescaling * dur, timescaling * dur], + [ax.get_ylim()[0], ax.get_ylim()[1]], + color=(0.312, 0.2, 0.33), + alpha=0.3, + ) + + # labels + if pos == 0: + ax.set_ylabel(f"({voltageunit})") + if pos == seqlen - 1 and not (isinstance(obj_to_plot, BluePrint)): + newax = ax.twinx() + newax.set_yticks([]) + if isinstance(chan, int): + new_ylabel = f"Ch. {chan}" + elif isinstance(chan, str): + new_ylabel = chan + newax.set_ylabel(new_ylabel) + + if seq[pos + 1]["type"] == "subsequence": + ax.set_xlabel("Time N/A") + else: + ax.set_xlabel(f"({timeunit})") + + # remove excess space from the plot + if not chanind + 1 == len(chans): + ax.set_xticks([]) + if not pos == 0: + ax.set_yticks([]) + fig.subplots_adjust(hspace=0, wspace=0) + + # display sequencer information + if chanind == 0 and isinstance(obj_to_plot, Sequence): + seq_info = seq[pos + 1]["sequencing"] + titlestring = "" + if seq_info["twait"] == 1: # trigger wait + titlestring += "T " + if seq_info["nrep"] > 1: # nreps + titlestring += "\u21bb{} ".format(seq_info["nrep"]) + if seq_info["nrep"] == 0: + titlestring += "\u221e " + if seq_info["jump_input"] != 0: + if seq_info["jump_input"] == -1: + titlestring += "E\u2192 " + else: + titlestring += "E{} ".format(seq_info["jump_input"]) + if seq_info["goto"] > 0: + titlestring += "\u21b1{}".format(seq_info["goto"]) + + ax.set_title(titlestring)
+ +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/ripasso.html b/_modules/broadbean/ripasso.html new file mode 100644 index 000000000..f7d374996 --- /dev/null +++ b/_modules/broadbean/ripasso.html @@ -0,0 +1,509 @@ + + + + + + + + broadbean.ripasso - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.ripasso

+# Module providing filter compensation. Developed for use with the broadbean
+# pulse building module, but provides a standalone API
+#
+# The name is (of course) a pun. Ripasso; first a filter, then a compensation,
+# i.e. something that is re-passed. Also not quite an Amarone...
+#
+
+import logging
+
+import numpy as np
+from numpy.fft import fft, fftfreq, ifft
+
+log = logging.getLogger(__name__)
+
+
+
+[docs] +class MissingFrequenciesError(Exception): + pass
+ + + +def _rcFilter(SR, npts, f_cut, kind="HP", order=1, DCgain=0): + """ + Nth order (RC circuit) filter + made with frequencies matching the fft output + """ + + freqs = fftfreq(npts, 1 / SR) + + tau = 1 / f_cut + top = 2j * np.pi + + if kind == "HP": + tf = top * tau * freqs / (1 + top * tau * freqs) + + # now, we have identically zero gain for the DC component, + # which makes the transfer function non-invertible + # + # It is a bit of an open question what DC compensation we want... + + tf[tf == 0] = DCgain # No DC suppression + + elif kind == "LP": + tf = 1 / (1 + top * tau * freqs) + + return tf**order + + +
+[docs] +def applyRCFilter(signal, SR, kind, f_cut, order, DCgain=0): + """ + Apply a simple RC-circuit filter + to signal and return the filtered signal. + + Args: + signal (np.array): The input signal. The signal is assumed to start at + t=0 and be evenly sampled at sample rate SR. + SR (int): Sample rate (Sa/s) of the input signal + kind (str): The type of filter. Either 'HP' or 'LP'. + f_cut (float): The cutoff frequency of the filter (Hz) + order (int): The order of the filter. The first order filter is + applied order times. + DCgain (Optional[float]): The DC gain of the filter. ONLY used by the + high-pass filter. Default: 0. + + Returns: + np.array: + The filtered signal along the original time axis. Imaginary + parts are discarded prior to return. + + Raises: + ValueError: If kind is neither 'HP' nor 'LP' + """ + + if kind not in ["HP", "LP"]: + raise ValueError('Please specify filter type as either "HP" or "LP".') + + N = len(signal) + transfun = _rcFilter(SR, N, f_cut, kind=kind, order=order, DCgain=DCgain) + output = ifft(fft(signal) * transfun) + output = np.real(output) + + return output
+ + + +
+[docs] +def applyInverseRCFilter(signal, SR, kind, f_cut, order, DCgain=1): + """ + Apply the inverse of an RC-circuit filter to a signal and return the + compensated signal. + + Note that a high-pass filter in principle has identically zero DC + gain which requires an infinite offset to compensate. + + Args: + signal (np.array): The input signal. The signal is assumed to start at + t=0 and be evenly sampled at sample rate SR. + SR (int): Sample rate (Sa/s) of the input signal + kind (str): The type of filter. Either 'HP' or 'LP'. + f_cut (float): The cutoff frequency of the filter (Hz) + order (int): The order of the filter. The first order filter is + applied order times. + DCgain (Optional[float]): The DC gain of the filter. ONLY used by the + high-pass filter. Default: 1. + + Returns: + np.array: + The filtered signal along the original time axis. Imaginary + parts are discarded prior to return. + + Raises: + ValueError: If kind is neither 'HP' nor 'LP' + ValueError: If DCgain is zero. + """ + + if kind not in ["HP", "LP"]: + raise ValueError( + 'Wrong filter type. Please specify filter type as either "HP" or "LP".' + ) + + if not DCgain > 0: + raise ValueError("Non-invertible DCgain! Please set DCgain to a finite value.") + + N = len(signal) + transfun = _rcFilter(SR, N, f_cut, order=-order, kind=kind, DCgain=DCgain) + output = ifft(fft(signal) * transfun) + output = np.real(output) + + return output
+ + + +
+[docs] +def applyCustomTransferFunction(signal, SR, tf_freqs, tf_amp, invert=False): + """ + Apply custom transfer function + + Given a signal, its sample rate, and a provided transfer function, apply + the transfer function to the signal. + + Args: + signal (np.array): A numpy array containing the signal + SR (int): The sample rate of the signal (Sa/s) + tf_freqs (np.array): The frequencies of the transfer function. Must + be monotonically increasing. + tf_amp (np.array): The amplitude of the transfer function. Must be + dimensionless. + invert (Optional[bool]): If True, the inverse transfer function is + applied. Default: False. + + Returns: + np.array: + The modified signal. + """ + + npts = len(signal) + + # validate tf_freqs + + df = np.diff(tf_freqs).round(6) + + if not np.sum(df > 0) == len(df): + raise ValueError( + "Invalid transfer function freq. axis. " + "Frequencies must be monotonically increasing." + ) + + if not tf_freqs[-1] >= SR / 2: + # TODO: think about whether this is a problem + # What is the desired behaviour for high frequencies if nothing + # is specified? I guess NOOP, i.e. the transfer func. is 1 + raise MissingFrequenciesError( + "Supplied transfer function does not " + "specify frequency response up to the " + "Nyquist frequency of the signal." + ) + + if not tf_freqs[0] == 0: + # what to do in this case? Extrapolate 1s? Make the user do this? + pass + + # Step 1: resample to fftfreq type axis + freqax = fftfreq(npts, 1 / SR) + freqax_pos = freqax[: npts // 2] + freqax_neg = freqax[npts // 2 :] + + resampled_pos = np.interp(freqax_pos, tf_freqs, tf_amp) + resampled_neg = np.interp(-freqax_neg[::-1], tf_freqs, tf_amp) + + transferfun = np.concatenate((resampled_pos, resampled_neg[::-1])) + + # Step 2: Apply transfer function + if invert: + power = -1 + else: + power = 1 + + signal_filtered = ifft(fft(signal) * (transferfun**power)) + imax = np.imag(signal_filtered).max() + log.debug( + "Applying custom transfer function. Discarding imag parts " + f"no larger than {imax}" + ) + signal_filtered = np.real(signal_filtered) + + return signal_filtered
+ +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/sequence.html b/_modules/broadbean/sequence.html new file mode 100644 index 000000000..51cf72d07 --- /dev/null +++ b/_modules/broadbean/sequence.html @@ -0,0 +1,1677 @@ + + + + + + + + broadbean.sequence - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.sequence

+# this file defines the sequence object
+# along with a few helpers
+import json
+import logging
+import warnings
+from copy import deepcopy
+from typing import Any, cast
+
+import numpy as np
+from schema import Optional, Or, Schema
+
+from broadbean.blueprint import BluePrint
+from broadbean.element import Element  # TODO: change import to element.py
+from broadbean.ripasso import applyInverseRCFilter
+
+from .broadbean import (
+    PulseAtoms,
+    _AWGOutput,
+    _channelListSorter,  # TODO: change import to helpers.py
+)
+
+log = logging.getLogger(__name__)
+
+fs_schema = Schema(
+    {
+        int: {
+            "type": Or("subsequence", "element"),
+            "content": {
+                int: {
+                    "data": {Or(str, int): {str: np.ndarray}},
+                    Optional("sequencing"): {Optional(str): int},
+                }
+            },
+            "sequencing": {Optional(str): int},
+        }
+    }
+)
+
+
+
+[docs] +class SequencingError(Exception): + pass
+ + + +
+[docs] +class SequenceConsistencyError(Exception): + pass
+ + + +
+[docs] +class InvalidForgedSequenceError(Exception): + pass
+ + + +
+[docs] +class SequenceCompatibilityError(Exception): + pass
+ + + +
+[docs] +class SpecificationInconsistencyError(Exception): + pass
+ + + +
+[docs] +class Sequence: + """ + Sequence object + """ + + def __init__(self): + """ + Not much to see here... + """ + + # the internal data structure, a dict with tuples as keys and values + # the key is sequence position (int), the value is element (Element) + # or subsequence (Sequence) + self._data = {} + + # Here goes the sequencing info. Key: position + # value: dict with keys 'twait', 'nrep', 'jump_input', + # 'jump_target', 'goto' + # + # the sequencing is filled out automatically with default values + # when an element is added + # Note that not all output backends use all items in the list + self._sequencing = {} + + # The dictionary to store AWG settings + # Keys will include: + # 'SR', 'channelX_amplitude', 'channelX_offset', 'channelX_filter' + self._awgspecs = {} + + # The metainfo to be extracted by measurements + # todo: I'm pretty sure this is obsolete now that description exists + self._meta = {} + + # some backends (seqx files) allow for a sequence to have a name + # we make the name a property of the sequence + self._name = "" + + def __eq__(self, other): + if not isinstance(other, Sequence): + return False + elif not self._data == other._data: + return False + elif not self._meta == other._meta: + return False + elif not self._awgspecs == other._awgspecs: + return False + elif not self._sequencing == other._sequencing: + return False + else: + return True + + def __add__(self, other): + """ + Add two sequences. + Return a new sequence with is the right argument appended to the + left argument. + """ + + # Validation + if not self.checkConsistency(): + raise SequenceConsistencyError("Left hand sequence inconsistent!") + if not other.checkConsistency(): + raise SequenceConsistencyError("Right hand sequence inconsistent!") + + if not self._awgspecs == other._awgspecs: + raise SequenceCompatibilityError( + "Incompatible sequences: different AWGspecifications." + ) + + newseq = Sequence() + N = len(self._data) + + newdata1 = {key: self.element(key).copy() for key in self._data.keys()} + newdata2 = {key + N: other.element(key).copy() for key in other._data.keys()} + newdata1.update(newdata2) + + newseq._data = newdata1 + + newsequencing1 = { + key: self._sequencing[key].copy() for key in self._sequencing.keys() + } + newsequencing2 = dict() + + for key, item in other._sequencing.items(): + newitem = item.copy() + # update goto and jump according to new sequence length + if newitem["goto"] > 0: + newitem["goto"] += N + if newitem["jump_target"] > 0: + newitem["jump_target"] += N + newsequencing2.update({key + N: newitem}) + + newsequencing1.update(newsequencing2) + + newseq._sequencing = newsequencing1 + + newseq._awgspecs = other._awgspecs.copy() + + return newseq + +
+[docs] + def copy(self): + """ + Returns a copy of the sequence. + """ + newseq = Sequence() + newseq._data = deepcopy(self._data) + newseq._meta = deepcopy(self._meta) + newseq._awgspecs = deepcopy(self._awgspecs) + newseq._sequencing = deepcopy(self._sequencing) + + return newseq
+ + +
+[docs] + def setSequenceSettings(self, pos, wait, nreps, jump, goto): + """ + Set the sequence setting for the sequence element at pos. + + Args: + pos (int): The sequence element (counting from 1) + wait (int): The wait state specifying whether to wait for a + trigger. 0: OFF, don't wait, 1: ON, wait. For some backends, + additional integers are allowed to specify the trigger input. + 0 always means off. + nreps (int): Number of repetitions. 0 corresponds to infinite + repetitions + jump (int): Event jump target, the position of a sequence element. + If 0, the event jump state is off. + goto (int): Goto target, the position of a sequence element. + 0 means next. + """ + + warnings.warn( + "Deprecation warning. This function is only compatible " + "with AWG5014 output and will be removed. " + "Please use the specific setSequencingXXX methods." + ) + + # Validation (some validation 'postponed' and put in checkConsistency) + # + # Because of different compliances for different backends, + # most validation of these settings is deferred and performed + # in the outputForXXX methods + + self._sequencing[pos] = { + "twait": wait, + "nrep": nreps, + "jump_target": jump, + "goto": goto, + "jump_input": 0, + }
+ + +
+[docs] + def setSequencingTriggerWait(self, pos: int, wait: int) -> None: + """ + Set the trigger wait for the sequence element at pos. For + AWG 5014 out, this can be 0 or 1, For AWG 70000A output, this + can be 0, 1, 2, or 3. + + Args: + pos: The sequence element (counting from 1) + wait: The wait state/input depending on backend. + """ + self._sequencing[pos]["twait"] = wait
+ + +
+[docs] + def setSequencingNumberOfRepetitions(self, pos: int, nrep: int) -> None: + """ + Set the number of repetitions for the sequence element at pos. + + Args: + pos: The sequence element (counting from 1) + nrep: The number of repetitions (0 means infinite) + """ + self._sequencing[pos]["nrep"] = nrep
+ + +
+[docs] + def setSequencingEventInput(self, pos: int, jump_input: int) -> None: + """ + Set the event input for the sequence element at pos. This setting is + ignored by the AWG 5014. + + Args: + pos: The sequence element (counting from 1) + jump_input: The input specifier, 0 for off, + 1 for 'TrigA', 2 for 'TrigB', 3 for 'Internal'. + """ + self._sequencing[pos]["jump_input"] = jump_input
+ + +
+[docs] + def setSequencingEventJumpTarget(self, pos: int, jump_target: int) -> None: + """ + Set the event jump target for the sequence element at pos. + + Args: + pos: The sequence element (counting from 1) + jump_target: The sequence element to jump to (counting from 1) + """ + self._sequencing[pos]["jump_target"] = jump_target
+ + +
+[docs] + def setSequencingGoto(self, pos: int, goto: int) -> None: + """ + Set the goto target (which element to play after the current one ends) + for the sequence element at pos. + + Args: + pos: The sequence element (counting from 1) + goto: The position of the element to play. 0 means 'next in line' + """ + self._sequencing[pos]["goto"] = goto
+ + +
+[docs] + def setSR(self, SR): + """ + Set the sample rate for the sequence + """ + self._awgspecs["SR"] = SR
+ + +
+[docs] + def setChannelVoltageRange(self, channel, ampl, offset): + """ + Assign the physical voltages of the channel. This is used when making + output for .awg files. The corresponding parameters in the QCoDeS + AWG5014 driver are called chXX_amp and chXX_offset. Please ensure that + the channel in question is indeed in ampl/offset mode and not in + high/low mode. + + Args: + channel (int): The channel number + ampl (float): The channel peak-to-peak amplitude (V) + offset (float): The channel offset (V) + """ + warnings.warn( + "Deprecation warning. This function is deprecated." + " Use setChannelAmplitude and SetChannelOffset " + "instead." + ) + + keystr = f"channel{channel}_amplitude" + self._awgspecs[keystr] = ampl + keystr = f"channel{channel}_offset" + self._awgspecs[keystr] = offset
+ + +
+[docs] + def setChannelAmplitude(self, channel: int | str, ampl: float) -> None: + """ + Assign the physical voltage amplitude of the channel. This is used + when making output for real instruments. + + Args: + channel: The channel number + ampl: The channel peak-to-peak amplitude (V) + """ + keystr = f"channel{channel}_amplitude" + self._awgspecs[keystr] = ampl
+ + +
+[docs] + def setChannelOffset(self, channel: int | str, offset: float) -> None: + """ + Assign the physical voltage offset of the channel. This is used + by some backends when making output for real instruments + + Args: + channel: The channel number/name + offset: The channel offset (V) + """ + keystr = f"channel{channel}_offset" + self._awgspecs[keystr] = offset
+ + +
+[docs] + def setChannelDelay(self, channel: int | str, delay: float) -> None: + """ + Assign a delay to a channel. This is used when making output for .awg + files. Use the delay to compensate for cable length differences etc. + Zeros are prepended to the waveforms to delay them and correspondingly + appended to non (or less) delayed channels. + + Args: + channel: The channel number/name + delay: The required delay (s) + + Raises: + ValueError: If a non-integer or non-non-negative channel number is + given. + """ + + self._awgspecs[f"channel{channel}_delay"] = delay
+ + +
+[docs] + def setChannelFilterCompensation( + self, + channel: str | int, + kind: str, + order: int = 1, + f_cut: float | None = None, + tau: float | None = None, + ) -> None: + """ + Specify a filter to compensate for. + + The specified channel will get a compensation (pre-distorion) to + compensate for the specified frequency filter. Just to be clear: + the INVERSE transfer function of the one you specify is applied. + Only compensation for simple RC-circuit type high pass and low + pass is supported. + + Args: + channel: The channel to apply this to. + kind: Either 'LP' or 'HP' + order: The order of the filter to compensate for. + May be negative. Default: 1. + f_cut: The cut_off frequency (Hz). + tau): The time constant (s). Note that + tau = 1/f_cut and that only one of the two can be specified. + + Raises: + ValueError: If kind is not 'LP' or 'HP' + ValueError: If order is not an int. + SpecificationInconsistencyError: If both f_cut and tau are given. + """ + + if kind not in ["HP", "LP"]: + raise ValueError( + 'Filter kind must either be "LP" (low pass) or "HP" (high pass).' + ) + if not isinstance(order, int): + raise ValueError("Filter order must be an integer.") + if (f_cut is not None) and (tau is not None): + raise SpecificationInconsistencyError( + "Can not specify BOTH a time constant and a cut-off frequency." + ) + + keystr = f"channel{channel}_filtercompensation" + self._awgspecs[keystr] = { + "kind": kind, + "order": order, + "f_cut": f_cut, + "tau": tau, + }
+ + +
+[docs] + def addElement(self, position: int, element: Element) -> None: + """ + Add an element to the sequence. Overwrites previous values. + + Args: + position (int): The sequence position of the element (lowest: 1) + element (Element): An element instance + + Raises: + ValueError: If the element has inconsistent durations + """ + + # Validation + element.validateDurations() + + # make a new copy of the element + newelement = element.copy() + + # Data mutation + self._data.update({position: newelement}) + + # insert default sequencing settings + self._sequencing[position] = { + "twait": 0, + "nrep": 1, + "jump_input": 0, + "jump_target": 0, + "goto": 0, + }
+ + +
+[docs] + def addSubSequence(self, position: int, subsequence: "Sequence") -> None: + """ + Add a subsequence to the sequence. Overwrites anything previously + assigned to this position. The subsequence can not contain any + subsequences itself. + + Args: + position: The sequence position (starting from 1) + subsequence: The subsequence to add + """ + if not isinstance(subsequence, Sequence): + raise ValueError( + "Subsequence must be a sequence object. " + "Received object of type " + f"{type(subsequence)}." + ) + + for elem in subsequence._data.values(): + if isinstance(elem, Sequence): + raise ValueError("Subsequences can not contain subsequences.") + + if subsequence.SR != self.SR: + raise ValueError( + "Subsequence SR does not match (main) sequence SR" + f". ({subsequence.SR} and {self.SR})." + ) + + self._data[position] = subsequence.copy() + + self._sequencing[position] = { + "twait": 0, + "nrep": 1, + "jump_input": 0, + "jump_target": 0, + "goto": 0, + }
+ + +
+[docs] + def checkConsistency(self, verbose=False): + """ + Checks wether the sequence can be built, i.e. wether all elements + have waveforms on the same channels and of the same length. + """ + # TODO: Give helpful info if the check fails + + try: + self._awgspecs["SR"] + except KeyError: + raise KeyError("No sample rate specified. Can not perform check") + + # First check that all sample rates agree + # Since all elements are validated on input, the SR exists + SRs = [elem.SR for elem in self._data.values()] + if SRs == []: # case of empty Sequence + SRs = [None] + if SRs.count(SRs[0]) != len(SRs): + failmssg = "checkConsistency failed: inconsistent sample rates." + log.info(failmssg) + if verbose: + print(failmssg) + return False + + # Then check that elements use the same channels + specchans = [] + for elem in self._data.values(): + chans = _channelListSorter(elem.channels) + specchans.append(chans) + if specchans == []: # case of empty Sequence + chans = None + specchans = [None] + if specchans.count(chans) != len(specchans): + failmssg = ( + "checkConsistency failed: different elements specify different channels" + ) + log.info(failmssg) + if verbose: + print(failmssg) + return False + + # TODO: must all elements have same length? Does any AWG require this? + + # Finally, check that all positions are filled + positions = list(self._data.keys()) + if positions == []: # case of empty Sequence + positions = [1] + if not positions == list(range(1, len(positions) + 1)): + failmssg = ( + "checkConsistency failed: inconsistent sequence" + "positions. Must be 1, 2, 3, ..." + ) + log.info(failmssg) + if verbose: + print(failmssg) + return False + + # If all three tests pass... + return True
+ + + @property + def description(self): + """ + Return a dictionary fully describing the Sequence. + """ + desc = {} + + for pos, elem in self._data.items(): + desc[str(pos)] = {} + desc[str(pos)]["channels"] = elem.description + try: + sequencing = self._sequencing[pos] + seqdict = { + "Wait trigger": sequencing["twait"], + "Repeat": sequencing["nrep"], + "jump_input": sequencing["jump_input"], + "jump_target": sequencing["jump_target"], + "Go to": sequencing["goto"], + } + desc[str(pos)]["sequencing"] = seqdict + except KeyError: + desc[str(pos)]["sequencing"] = "Not set" + desc["awgspecs"] = self._awgspecs + return desc + +
+[docs] + def write_to_json(self, path_to_file: str) -> None: + """ + Writes sequences to JSON file + + Args: + path_to_file: the path to the file to write to ex: + path_to_file/sequense.json + """ + with open(path_to_file, "w") as fp: + json.dump(self.description, fp, indent=4)
+ + +
+[docs] + @classmethod + def sequence_from_description(cls, seq_dict: dict) -> "Sequence": + """ + Returns a sequence from a description given as a dict + + Args: + seq_dict: a dict in the same form as returned by + Sequence.description + """ + + awgspecs = seq_dict["awgspecs"] + SR = awgspecs["SR"] + elem_list = list(seq_dict.keys()) + new_instance = cls() + + for ele in elem_list[:-1]: + channels_list = list(seq_dict[ele]["channels"].keys()) + elem = Element() + for chan in channels_list: + bp_sum = BluePrint.blueprint_from_description( + seq_dict[ele]["channels"][chan] + ) + bp_sum.setSR(SR) + elem.addBluePrint(int(chan), bp_sum) + if "flags" in seq_dict[ele]["channels"][chan]: + flags = seq_dict[ele]["channels"][chan]["flags"] + elem.addFlags(int(chan), flags) + ChannelAmplitude = awgspecs[f"channel{chan}_amplitude"] + new_instance.setChannelAmplitude( + int(chan), ChannelAmplitude + ) # Call signature: channel, amplitude (peak-to-peak) + ChannelOffset = awgspecs[f"channel{chan}_offset"] + new_instance.setChannelOffset(int(chan), ChannelOffset) + + new_instance.addElement(int(ele), elem) + sequencedict = seq_dict[ele]["sequencing"] + new_instance.setSequencingTriggerWait( + int(ele), sequencedict["Wait trigger"] + ) + new_instance.setSequencingNumberOfRepetitions( + int(ele), sequencedict["Repeat"] + ) + new_instance.setSequencingEventInput(int(ele), sequencedict["jump_input"]) + new_instance.setSequencingEventJumpTarget( + int(ele), sequencedict["jump_target"] + ) + new_instance.setSequencingGoto(int(ele), sequencedict["Go to"]) + new_instance.setSR(SR) + return new_instance
+ + +
+[docs] + @classmethod + def init_from_json(cls, path_to_file: str) -> "Sequence": + """ + Reads sequense from JSON file + + Args: + path_to_file: the path to the file to be read ex: + path_to_file/sequense.json + This function is the inverse of write_to_json + The JSON file needs to be structured as if it was writen + by the function write_to_json + """ + new_instance = cls() + with open(path_to_file) as fp: + data_loaded = json.load(fp) + + new_instance = Sequence.sequence_from_description(data_loaded) + return new_instance
+ + + @property + def name(self): + return self._name + + @name.setter + def name(self, newname): + if not isinstance(newname, str): + raise ValueError("The sequence name must be a string") + self._name = newname + + @property + def length_sequenceelements(self): + """ + Returns the current number of specified sequence elements + """ + return len(self._data) + + @property + def SR(self): + """ + Returns the sample rate, if defined. Else returns -1. + """ + try: + SR = self._awgspecs["SR"] + except KeyError: + SR = -1 + + return SR + + @property + def channels(self): + """ + Returns a list of the specified channels of the sequence + """ + if self.checkConsistency(): + return self.element(1).channels + else: + raise SequenceConsistencyError( + "Sequence not consistent. Can not figure out the channels." + ) + + @property + def points(self): + """ + Returns the number of points of the sequence, disregarding + sequencing info (like repetitions). Useful for asserting upload + times, i.e. the size of the built sequence. + """ + total = 0 + for elem in self._data.values(): + total += elem.points + return total + + @property + def duration(self) -> float: + """ + Returns the duration in seconds of the sequence. + """ + duration = 0.0 + for pos, elem in self._data.items(): + nrep = self._sequencing[pos]["nrep"] + duration += nrep * elem.duration + return duration + +
+[docs] + def element(self, pos): + """ + Returns the element at the given position. Changes made to the return + value of this methods will apply to the sequence. If this is undesired, + make a copy of the returned element using Element.copy + + Args: + pos (int): The sequence position + + Raises: + KeyError: If no element is specified at the given position + """ + try: + elem = self._data[pos] + except KeyError: + raise KeyError(f"No element specified at sequence position {pos}") + + return elem
+ + + @staticmethod + def _plotSummary(seq: dict[int, dict]) -> dict[int, dict[str, np.ndarray]]: + """ + Return a plotting summary of a subsequence. + + Args: + seq: The 'content' value of a forged sequence where a + subsequence resides + + Returns: + A dict that looks like a forged element, but all waveforms + are just two points, np.array([min, max]) + """ + + output = {} + + # we assume correctness, all postions specify the same channels + chans = seq[1]["data"].keys() + + minmax = dict(zip(chans, [(0, 0)] * len(chans))) + + for element in seq.values(): + arr_dict = element["data"] + + for chan in chans: + wfm = arr_dict[chan]["wfm"] + if wfm.min() < minmax[chan][0]: + minmax[chan] = (wfm.min(), minmax[chan][1]) + if wfm.max() > minmax[chan][1]: + minmax[chan] = (minmax[chan][0], wfm.max()) + output[chan] = { + "wfm": np.array(minmax[chan]), + "m1": np.zeros(2), + "m2": np.zeros(2), + "time": np.linspace(0, 1, 2), + } + + return output + +
+[docs] + def forge( + self, + apply_delays: bool = True, + apply_filters: bool = True, + includetime: bool = False, + ) -> dict[int, dict]: + """ + Forge the sequence, applying all specified transformations + (delays and ripasso filter corrections). Copies the data, so + that the sequence is not modified by forging. + + Args: + apply_delays: Whether to apply the assigned channel delays + (if any) + apply_filters: Whether to apply the assigned channel filters + (if any) + includetime: Whether to include the time axis and the segment + durations (a list) with the arrays. Used for plotting. + + Returns: + A nested dictionary holding the forged sequence. + """ + # Validation + if not self.checkConsistency(): + raise ValueError( + "Can not generate output. Something is " + "inconsistent. Please run " + "checkConsistency(verbose=True) for more details" + ) + + output: dict[int, dict] = {} + channels = self.channels + data = deepcopy(self._data) + seqlen = len(data.keys()) + + # TODO: in this function, we iterate through the sequence three times + # It is probably worth considering refactoring that into a single + # iteration, although that may compromise readability + + # Apply channel delays. + + if apply_delays: + delays = [] + for chan in channels: + try: + delays.append(self._awgspecs[f"channel{chan}_delay"]) + except KeyError: + delays.append(0) + + for pos in range(1, seqlen + 1): + if isinstance(data[pos], Sequence): + subseq = data[pos] + for elem in subseq._data.values(): + elem._applyDelays(delays) + elif isinstance(data[pos], Element): + data[pos]._applyDelays(delays) + + # forge arrays and form the output dict + for pos in range(1, seqlen + 1): + output[pos] = {} + output[pos]["sequencing"] = self._sequencing[pos] + if isinstance(data[pos], Sequence): + subseq = data[pos] + output[pos]["type"] = "subsequence" + output[pos]["content"] = {} + for pos2 in range(1, subseq.length_sequenceelements + 1): + output[pos]["content"][pos2] = {"data": {}, "sequencing": {}} + elem = subseq.element(pos2) + dictdata = elem.getArrays(includetime=includetime) + output[pos]["content"][pos2]["data"] = dictdata + seqing = subseq._sequencing[pos2] + output[pos]["content"][pos2]["sequencing"] = seqing + # TODO: update sequencing + elif isinstance(data[pos], Element): + elem = data[pos] + output[pos]["type"] = "element" + dictdata = elem.getArrays(includetime=includetime) + output[pos]["content"] = {1: {"data": dictdata}} + + # apply filter corrections to forged arrays + if apply_filters: + for pos1 in range(1, seqlen + 1): + thiselem = output[pos1]["content"] + for pos2 in thiselem.keys(): + data = thiselem[pos2]["data"] + for channame in data.keys(): + keystr = f"channel{channame}_filtercompensation" + if keystr in self._awgspecs.keys(): + kind = self._awgspecs[keystr]["kind"] + order = self._awgspecs[keystr]["order"] + f_cut = self._awgspecs[keystr]["f_cut"] + tau = self._awgspecs[keystr]["tau"] + if f_cut is None: + f_cut = 1 / tau + prefilter = data[channame]["wfm"] + postfilter = applyInverseRCFilter( + prefilter, self.SR, kind, f_cut, order, DCgain=1 + ) + ( + output[pos1]["content"][pos2]["data"][channame]["wfm"] + ) = postfilter + + return output
+ + + def _prepareForOutputting(self) -> list[dict[int, Any]]: + """ + The preparser for numerical output. Applies delay and ripasso + corrections. + + Returns: + A list of outputs of the Element's getArrays functions, i.e. + a list of dictionaries with key position (int) and value + an np.ndarray of array([wfm, m1, m2, time]), where the + wfm values are still in V. The particular backend output + function must rescale to the specific format it adheres to. + """ + # Validation + if not self.checkConsistency(): + raise ValueError( + "Can not generate output. Something is " + "inconsistent. Please run " + "checkConsistency(verbose=True) for more details" + ) + # + # + channels = self.element(1).channels # all elements have ident. chans + # We copy the data so that the state of the Sequence is left unaltered + # by outputting for AWG + data = deepcopy(self._data) + seqlen = len(data.keys()) + # check if sequencing information is specified for each element + if not sorted(list(self._sequencing.keys())) == list(range(1, seqlen + 1)): + raise ValueError( + "Can not generate output for file; incorrect sequencer information." + ) + + # Verify physical amplitude specifiations + for chan in channels: + ampkey = f"channel{chan}_amplitude" + if ampkey not in self._awgspecs.keys(): + raise KeyError( + "No amplitude specified for channel {chan}. Can not continue." + ) + + # Apply channel delays. + delays = [] + for chan in channels: + try: + delays.append(self._awgspecs[f"channel{chan}_delay"]) + except KeyError: + delays.append(0) + maxdelay = max(delays) + + for pos in range(1, seqlen + 1): + for chanind, chan in enumerate(channels): + element = data[pos] + delay = delays[chanind] + + if "blueprint" in element._data[chan].keys(): + blueprint = element._data[chan]["blueprint"] + # prevent information about flags to be lost + if "flags" in element._data[chan].keys(): + flags = element._data[chan]["flags"] + else: + flags = None + + # update existing waituntils + for segpos in range(len(blueprint._funlist)): + if blueprint._funlist[segpos] == "waituntil": + oldwait = blueprint._argslist[segpos][0] + blueprint._argslist[segpos] = (oldwait + delay,) + # insert delay before the waveform + if delay > 0: + blueprint.insertSegment(0, "waituntil", (delay,), "waituntil") + # add zeros at the end + if maxdelay - delay > 0: + blueprint.insertSegment( + -1, PulseAtoms.ramp, (0, 0), dur=maxdelay - delay + ) + # TODO: is the next line even needed? + # If not, remove the code updating the flags below + # and the one remembering them above + element.addBluePrint(chan, blueprint) + if flags is not None: + element.addFlags(chan, flags) + + else: + arrays = element._data[chan]["array"] + for name, arr in arrays.items(): + pre_wait = np.zeros(int(delay / self.SR)) + post_wait = np.zeros(int((maxdelay - delay) / self.SR)) + arrays[name] = np.concatenate((pre_wait, arr, post_wait)) + + # Now forge all the elements as specified + elements = [] # the forged elements + for pos in range(1, seqlen + 1): + elements.append(data[pos].getArrays()) + + # Now that the numerical arrays exist, we can apply filter compensation + for chan in channels: + keystr = f"channel{chan}_filtercompensation" + if keystr in self._awgspecs.keys(): + kind = self._awgspecs[keystr]["kind"] + order = self._awgspecs[keystr]["order"] + f_cut = self._awgspecs[keystr]["f_cut"] + tau = self._awgspecs[keystr]["tau"] + if f_cut is None: + f_cut = 1 / tau + for pos in range(seqlen): + prefilter = elements[pos][chan]["wfm"] + postfilter = applyInverseRCFilter( + prefilter, self.SR, kind, f_cut, order, DCgain=1 + ) + elements[pos][chan]["wfm"] = postfilter + + return elements + +
+[docs] + def outputForSEQXFile( + self, + ) -> tuple[ + list[int], + list[int], + list[int], + list[int], + list[int], + list[list[np.ndarray]], + list[float], + str, + ]: + """ + Generate a tuple matching the call signature of the QCoDeS + AWG70000A driver's `makeSEQXFile` function. If channel delays + have been specified, they are added to the ouput before exporting. + The intended use of this function together with the QCoDeS driver is + + .. code:: python + + pkg = seq.outputForSEQXFile() + seqx = awg70000A.makeSEQXFile(*pkg) + + Returns: + A tuple holding (trig_waits, nreps, event_jumps, event_jump_to, + go_to, wfms, amplitudes, seqname) + """ + + # most of the footwork is done by the following function + elements = self._prepareForOutputting() + # _prepareForOutputting asserts that channel amplitudes and + # full sequencing is specified + seqlen = len(elements) + # all elements have ident. chans since _prepareForOutputting + # did not raise an exception + channels = self.element(1).channels + + for chan in channels: + offkey = f"channel{chan}_offset" + if offkey in self._awgspecs.keys(): + log.warning( + "Found a specified offset for channel " + f"{chan}, but .seqx files can't contain offset " + "information. Will ignore the offset." + "" + ) + + # now check that the amplitudes are within the allowed limits + # also verify that all waveforms are at least 2400 points + # No rescaling because the driver's _makeWFMXBinaryData does + # the rescaling + + amplitudes = [] + for chan in channels: + ampl = self._awgspecs[f"channel{chan}_amplitude"] + amplitudes.append(ampl) + if len(amplitudes) == 1: + amplitudes.append(0) + + for pos in range(1, seqlen + 1): + element = elements[pos - 1] + for chan in channels: + ampl = self._awgspecs[f"channel{chan}_amplitude"] + wfm = element[chan]["wfm"] + # check the waveform length + if len(wfm) < 2400: + raise ValueError( + "Waveform too short on channel " + f"{chan} at step {pos}; only {len(wfm)} points. " + "The required minimum is 2400 points." + "" + ) + # check whether the waveform voltages can be realised + if wfm.max() > ampl / 2: + raise ValueError( + "Waveform voltages exceed channel range " + f"on channel {chan}" + f" sequence element {pos}." + f" {wfm.max()} > {ampl / 2}!" + ) + if wfm.min() < -ampl / 2: + raise ValueError( + "Waveform voltages exceed channel range " + f"on channel {chan}" + f" sequence element {pos}. " + f"{wfm.min()} < {-ampl / 2}!" + ) + element[chan]["wfm"] = wfm + elements[pos - 1] = element + + # Finally cast the lists into the shapes required by the AWG driver + + waveforms = cast(list[list[np.ndarray]], [[] for dummy in range(len(channels))]) + nreps = [] + trig_waits = [] + gotos = [] + jump_states = [] + jump_tos = [] + + # Since sequencing options are valid/invalid differently for + # different backends, we make the validation here + for pos in range(1, seqlen + 1): + for chanind, chan in enumerate(channels): + wfm = elements[pos - 1][chan]["wfm"] + m1 = elements[pos - 1][chan]["m1"] + m2 = elements[pos - 1][chan]["m2"] + waveforms[chanind].append(np.array([wfm, m1, m2])) + + twait = self._sequencing[pos]["twait"] + nrep = self._sequencing[pos]["nrep"] + jump_to = self._sequencing[pos]["jump_target"] + jump_state = self._sequencing[pos]["jump_input"] + goto = self._sequencing[pos]["goto"] + + if twait not in [0, 1, 2, 3]: + raise SequencingError( + "Invalid trigger input at position" + f"{pos}: {twait}. Must be 0, 1, 2, or 3." + "" + ) + + if jump_state not in [0, 1, 2, 3]: + raise SequencingError( + "Invalid event jump input at position" + f"{pos}: {twait}. Must be either 0, 1, 2, or 3." + "" + ) + + if nrep not in range(0, 16384): + raise SequencingError( + "Invalid number of repetions at position" + f"{pos}: {nrep}. Must be either 0 (infinite) " + "or 1-16,383." + ) + + if jump_to not in range(-1, seqlen + 1): + raise SequencingError( + "Invalid event jump target at position" + f"{pos}: {jump_to}. Must be either -1 (next)," + f" 0 (off), or 1-{seqlen}." + "" + ) + + if goto not in range(0, seqlen + 1): + raise SequencingError( + "Invalid goto target at position" + f"{pos}: {goto}. Must be either 0 (next)," + f" or 1-{seqlen}." + "" + ) + + trig_waits.append(twait) + nreps.append(nrep) + jump_tos.append(jump_to) + jump_states.append(jump_state) + gotos.append(goto) + + return ( + trig_waits, + nreps, + jump_states, + jump_tos, + gotos, + waveforms, + amplitudes, + self.name, + )
+ + +
+[docs] + def outputForSEQXFileWithFlags( + self, + ) -> tuple[ + list[int], + list[int], + list[int], + list[int], + list[int], + list[list[np.ndarray]], + list[float], + str, + list[list[list[int]]], + ]: + """ + Generate a tuple matching the call signature of the QCoDeS + AWG70000A driver's `makeSEQXFile` function. Same as outputForSEQXFile(), + but also includes information about the flags. + + Returns: + A tuple holding (trig_waits, nreps, event_jumps, event_jump_to, + go_to, wfms, amplitudes, seqname, flags) + """ + + elements = self._prepareForOutputting() + seqlen = len(elements) + channels = self.element(1).channels + + # add flags for every element and channel + all_flags = [] + for chanind, chan in enumerate(channels): + flags_pos = [] + for pos in range(1, seqlen + 1): + if "flags" in elements[pos - 1][chan]: + flags = elements[pos - 1][chan]["flags"].tolist() + else: + flags = [0, 0, 0, 0] + flags_pos.append(flags) + all_flags.append(flags_pos) + + return self.outputForSEQXFile() + (all_flags,)
+ + +
+[docs] + def outputForAWGFile(self): + """ + Returns a sliceable object with items matching the call + signature of the 'make_*_awg_file' functions of the QCoDeS + AWG5014 driver. One may then construct an awg file as follows + (assuming that seq is the sequence object): + + .. code:: python + + package = seq.outputForAWGFile() + make_awg_file(*package[:], **kwargs) + + + """ + + elements = self._prepareForOutputting() + seqlen = len(elements) + # all elements have ident. chans since _prepareForOutputting + # did not raise an exception + channels = self.element(1).channels + + for chan in channels: + offkey = f"channel{chan}_offset" + if offkey not in self._awgspecs.keys(): + raise ValueError( + f"No specified offset for channel {chan}, can not continue." + ) + + # Apply channel scaling + # We must rescale to the interval -1, 1 where 1 is ampl/2+off and -1 is + # -ampl/2+off. + # + def rescaler(val, ampl, off): + return val / ampl * 2 - off + + for pos in range(1, seqlen + 1): + element = elements[pos - 1] + for chan in channels: + ampl = self._awgspecs[f"channel{chan}_amplitude"] + off = self._awgspecs[f"channel{chan}_offset"] + wfm = element[chan]["wfm"] + # check whether the waveform voltages can be realised + if wfm.max() > ampl / 2 + off: + raise ValueError( + "Waveform voltages exceed channel range " + f"on channel {chan}" + f" sequence element {pos}." + f" {wfm.max()} > {ampl / 2 + off}!" + ) + if wfm.min() < -ampl / 2 + off: + raise ValueError( + "Waveform voltages exceed channel range " + f"on channel {chan}" + f" sequence element {pos}. " + f"{wfm.min()} < {-ampl / 2 + off}!" + ) + wfm = rescaler(wfm, ampl, off) + element[chan]["wfm"] = wfm + elements[pos - 1] = element + + # Finally cast the lists into the shapes required by the AWG driver + waveforms = [[] for dummy in range(len(channels))] + m1s = [[] for dummy in range(len(channels))] + m2s = [[] for dummy in range(len(channels))] + nreps = [] + trig_waits = [] + gotos = [] + jump_tos = [] + + # Since sequencing options are valid/invalid differently for + # different backends, we make the validation here + for pos in range(1, seqlen + 1): + for chanind, chan in enumerate(channels): + waveforms[chanind].append(elements[pos - 1][chan]["wfm"]) + m1s[chanind].append(elements[pos - 1][chan]["m1"]) + m2s[chanind].append(elements[pos - 1][chan]["m2"]) + + twait = self._sequencing[pos]["twait"] + nrep = self._sequencing[pos]["nrep"] + jump_to = self._sequencing[pos]["jump_target"] + goto = self._sequencing[pos]["goto"] + + if twait not in [0, 1]: + raise SequencingError( + "Invalid trigger wait state at position" + f"{pos}: {twait}. Must be either 0 or 1." + "" + ) + + if nrep not in range(0, 65537): + raise SequencingError( + "Invalid number of repetions at position" + f"{pos}: {nrep}. Must be either 0 (infinite) " + "or 1-65,536." + ) + + if jump_to not in range(-1, seqlen + 1): + raise SequencingError( + "Invalid event jump target at position" + f"{pos}: {jump_to}. Must be either -1 (next)," + f" 0 (off), or 1-{seqlen}." + "" + ) + + if goto not in range(0, seqlen + 1): + raise SequencingError( + "Invalid goto target at position" + f"{pos}: {goto}. Must be either 0 (next)," + f" or 1-{seqlen}." + "" + ) + + trig_waits.append(twait) + nreps.append(nrep) + jump_tos.append(jump_to) + gotos.append(goto) + + # ...and make a sliceable object out of them + output = _AWGOutput( + (waveforms, m1s, m2s, nreps, trig_waits, gotos, jump_tos), self.channels + ) + + return output
+
+ +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/broadbean/tools.html b/_modules/broadbean/tools.html new file mode 100644 index 000000000..58e9dcea2 --- /dev/null +++ b/_modules/broadbean/tools.html @@ -0,0 +1,481 @@ + + + + + + + + broadbean.tools - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+

Source code for broadbean.tools

+# High-level tool for sequence building and manipulation
+#
+
+import logging
+
+import numpy as np
+
+from broadbean.sequence import Sequence, SequenceConsistencyError
+
+log = logging.getLogger(__name__)
+
+
+
+[docs] +def makeLinearlyVaryingSequence(baseelement, channel, name, arg, start, stop, step): + """ + Make a pulse sequence where a single parameter varies linearly. + The pulse sequence will consist of N copies of the same element with just + the specified argument changed (N = abs(stop-start)/steps) + + Args: + baseelement (Element): The basic element. + channel (int): The channel where the change should happen + name (str): Name of the blueprint segment to change + arg (Union[str, int]): Name (str) or position (int) of the argument + to change. If the arg is 'duration', the duration is changed + instead. + start (float): Start point of the variation (included) + stop (float): Stop point of the variation (included) + step (float): Increment of the variation + """ + + # TODO: validation + # TODO: Make more general varyer and refactor code + + sequence = Sequence() + + sequence.setSR(baseelement.SR) + + iterator = np.linspace(start, stop, round(abs(stop - start) / step) + 1) + + for ind, val in enumerate(iterator): + element = baseelement.copy() + if arg == "duration": + element.changeDuration(channel, name, val) + else: + element.changeArg(channel, name, arg, val) + sequence.addElement(ind + 1, element) + + return sequence
+ + + +
+[docs] +def makeVaryingSequence(baseelement, channels, names, args, iters): + """ + Make a pulse sequence where N parameters vary simultaneously in M steps. + The user inputs a baseelement which is copied M times and changed + according to the given inputs. + + Args: + baseelement (Element): The basic element. + channels (Union[list, tuple]): Either a list or a tuple of channels on + which to find the blueprint to change. Must have length N. + names (Union[list, tuple]): Either a list or a tuple of names of the + segment to change. Must have length N. + args (Union[list, tuple]): Either a list or a tuple of argument + specifications for the argument to change. Use 'duration' to change + the segment duration. Must have length N. + iters (Union[list, tuple]): Either a list or a tuple of length N + containing Union[list, tuple, range] of length M. + + Raises: + ValueError: If not channels, names, args, and iters are of the same + length. + ValueError: If not each iter in iters specifies the same number of + values. + """ + + # Validation + baseelement.validateDurations() + + inputlengths = [len(channels), len(names), len(args), len(iters)] + if not inputlengths.count(inputlengths[0]) == len(inputlengths): + raise ValueError( + "Inconsistent number of channel, names, args, and " + "parameter sequences. Please specify the same number " + "of each." + ) + noofvals = [len(itr) for itr in iters] + if not noofvals.count(noofvals[0]) == len(iters): + raise ValueError( + "Not the same number of values in each parameter " + "value sequence (input argument: iters)" + ) + + sequence = Sequence() + sequence.setSR(baseelement.SR) + + for elnum in range(1, noofvals[0] + 1): + sequence.addElement(elnum, baseelement.copy()) + + for chan, name, arg, vals in zip(channels, names, args, iters): + for mpos, val in enumerate(vals): + element = sequence.element(mpos + 1) + if arg == "duration": + element.changeDuration(chan, name, val) + else: + element.changeArg(chan, name, arg, val) + + log.info("Created varying sequence using makeVaryingSequence. Now validating it...") + + if not sequence.checkConsistency(): + raise SequenceConsistencyError("Invalid sequence. See log for details.") + else: + log.info("Valid sequence") + return sequence
+ + + +
+[docs] +def repeatAndVarySequence(seq, poss, channels, names, args, iters): + """ + Repeat a sequence and vary part(s) of it. Returns a new sequence. + Given N specifications of M steps, N parameters are varied in M + steps. + + Args: + seq (Sequence): The sequence to be repeated. + poss (Union[list, tuple]): A length N list/tuple specifying at which + sequence position(s) the blueprint to change is. + channels (Union[list, tuple]): A length N list/tuple specifying on + which channel(s) the blueprint to change is. + names (Union[list, tuple]): A length N list/tuple specifying the name + of the segment to change. + args (Union[list, tuple]): A length N list/tuple specifying which + argument to change. A valid argument is also 'duration'. + iters (Union[list, tuple]): A length N list/tuple containing length + M indexable iterables with the values to step through. + """ + + if not seq.checkConsistency(): + raise SequenceConsistencyError( + "Inconsistent input sequence! Can not " + "proceed. Check all positions " + "and channels." + ) + + inputlens = [len(poss), len(channels), len(names), len(args), len(iters)] + if not inputlens.count(inputlens[0]) == len(inputlens): + raise ValueError( + "Inconsistent number of position, channel, name, args" + ", and " + "parameter sequences. Please specify the same number " + "of each." + ) + noofvals = [len(itr) for itr in iters] + if not noofvals.count(noofvals[0]) == len(iters): + raise ValueError( + "Not the same number of values in each parameter " + "value sequence (input argument: iters)" + ) + + newseq = Sequence() + newseq._awgspecs = seq._awgspecs + + no_of_steps = noofvals[0] + + for step in range(no_of_steps): + tempseq = seq.copy() + for pos, chan, name, arg, vals in zip(poss, channels, names, args, iters): + element = tempseq.element(pos) + val = vals[step] + + if arg == "duration": + element.changeDuration(chan, name, val) + else: + element.changeArg(chan, name, arg, val) + newseq = newseq + tempseq + + return newseq
+ +
+
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_modules/index.html b/_modules/index.html new file mode 100644 index 000000000..6cd18f237 --- /dev/null +++ b/_modules/index.html @@ -0,0 +1,304 @@ + + + + + + + + Overview: module code - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+ +
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/_sources/api/generated/broadbean.rst.txt b/_sources/api/generated/broadbean.rst.txt new file mode 100644 index 000000000..396083cd7 --- /dev/null +++ b/_sources/api/generated/broadbean.rst.txt @@ -0,0 +1,69 @@ +broadbean package +================= + +Submodules +---------- + +broadbean.blueprint module +-------------------------- + +.. automodule:: broadbean.blueprint + :members: + :undoc-members: + :show-inheritance: + +broadbean.broadbean module +-------------------------- + +.. automodule:: broadbean.broadbean + :members: + :undoc-members: + :show-inheritance: + +broadbean.element module +------------------------ + +.. automodule:: broadbean.element + :members: + :undoc-members: + :show-inheritance: + +broadbean.plotting module +------------------------- + +.. automodule:: broadbean.plotting + :members: + :undoc-members: + :show-inheritance: + +broadbean.ripasso module +------------------------ + +.. automodule:: broadbean.ripasso + :members: + :undoc-members: + :show-inheritance: + +broadbean.sequence module +------------------------- + +.. automodule:: broadbean.sequence + :members: + :undoc-members: + :show-inheritance: + +broadbean.tools module +---------------------- + +.. automodule:: broadbean.tools + :members: + :undoc-members: + :show-inheritance: + +Module contents +--------------- + +.. automodule:: broadbean + :members: + :undoc-members: + :show-inheritance: diff --git a/_sources/changes/0.10.0.rst.txt b/_sources/changes/0.10.0.rst.txt new file mode 100644 index 000000000..b6791eaa9 --- /dev/null +++ b/_sources/changes/0.10.0.rst.txt @@ -0,0 +1,18 @@ +Changelog for broadbean 0.10.0 +============================== + +The January 2021 release of broadbean. + + +Breaking Changes: +_________________ + +There are no breaking changes in this release of broadbean + + +New: +____ + +- Support for reading and writing broadbean Sequences, elements and bluprints to and from a json file +- made broadbean compatiple with numpy version 1.18 +- Include LICENSE in package diff --git a/_sources/changes/0.11.0.rst.txt b/_sources/changes/0.11.0.rst.txt new file mode 100644 index 000000000..28b7e745f --- /dev/null +++ b/_sources/changes/0.11.0.rst.txt @@ -0,0 +1,38 @@ +Changelog for broadbean 0.11.0 +============================== + +The August 2022 release of broadbean with many great improvements for modernizing broadbean's infrastructure. + + +Breaking Changes: +_________________ + +- Methods and functions marked for deletion in version `0.10.0` have now been removed. Specifically + `BluePrint.plot`, `broadbean.bluePrintPlotter`, `Element.plotElement`, `Sequence.plotSequence` + and `Sequence.plotAWGOutput`. (#107) + +New: +____ + +- New `Sequence` method `outputForSEQXFileWithFlags` for setting flags for every element forming a sequence. + This is useful for auxiliary outputs on a Tektronix AWG70000. (#101) + +Improved: +_________ + +- Fix for invalid identity comparisons in blueprint submodule. (#102) + + +Behind The Scenes: +__________________ + +- Updated `README.md` that includes updated link to `broadbean` documentation. (#158) +- Replace Conda test job with regular pip job on windows. (#139) +- Enable dependabot for `broadbean`. (#111) +- Enable precommit hook. (#110) +- Documentation infrastructure improvements. (#110) +- Modernize setup and build infrastructure (convert to pep516/517, build wheels and sdist using build, + automatic upload to pypi, move config to pyproject.toml and setup.cfg and pinning dependencies with + requirements.txt). (#109) +- Move tests into package to include them into distribution. (#108) +- Use GitHub actions, test on python 3.7-3.10, remove python 3.6 support, remove Travis and AppVeyor. (#103) diff --git a/_sources/changes/index.rst.txt b/_sources/changes/index.rst.txt new file mode 100644 index 000000000..dd308f203 --- /dev/null +++ b/_sources/changes/index.rst.txt @@ -0,0 +1,6 @@ +Changelogs +========== + +.. toctree:: + 0.11.0 <0.11.0> + 0.10.0 <0.10.0> diff --git a/_sources/examples/Example_Write_Read_JSON.ipynb.txt b/_sources/examples/Example_Write_Read_JSON.ipynb.txt new file mode 100644 index 000000000..3020a1ecf --- /dev/null +++ b/_sources/examples/Example_Write_Read_JSON.ipynb.txt @@ -0,0 +1,7096 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Read and Write from JSON file Tutorial" + ] + }, + { + "cell_type": "code", + "execution_count": 73, + "metadata": {}, + "outputs": [], + "source": [ + "#\n", + "# IMPORTS\n", + "#\n", + "%matplotlib nbagg\n", + "import matplotlib as mpl\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "mpl.rcParams[\"figure.figsize\"] = (8, 3)\n", + "mpl.rcParams[\"figure.subplot.bottom\"] = 0.15" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write BluePrint from JSON file Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Building a BluePrint" + ] + }, + { + "cell_type": "code", + "execution_count": 74, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "plotter(bp_boxes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Writing the BluePrint to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 75, + "metadata": {}, + "outputs": [], + "source": [ + "bp_boxes.write_to_json(\"blue.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Reading the BluePrint back from the file" + ] + }, + { + "cell_type": "code", + "execution_count": 76, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bp_boxes_read = bb.BluePrint.init_from_json(\"blue.json\")\n", + "bp_boxes_read.setSR(\n", + " 1e9\n", + ") # note the blueprint readback do not have a sample rate attached\n", + "plotter(bp_boxes_read)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test if the BluePrints are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 77, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(bp_boxes == bp_boxes_read)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write Element from JSON file Tutorial\n", + "Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array " + ] + }, + { + "cell_type": "code", + "execution_count": 78, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "######################################################\n", + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", + "seq1 = bb.Sequence()\n", + "\n", + "# We fill up the sequence by adding elements at different sequence positions.\n", + "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", + "\n", + "#\n", + "# Make blueprints, make elements\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"ramp\", (-0.0, 100e-9), 1\n", + ") # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"sine\", (-0.0, 100e-9), 2\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", + "\n", + "\n", + "# create elements\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Writing the Element to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 79, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "elem1.write_to_json(\"elem.json\")\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Reading the Element back from the file" + ] + }, + { + "cell_type": "code", + "execution_count": 80, + "metadata": {}, + "outputs": [], + "source": [ + "elem_read = bb.Element.init_from_json(\"elem.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test if the Element.description are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 81, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(\n", + " elem1.description == elem_read.description\n", + ") ## note that the elements are not identical only the discription" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write Sequence from JSON file Tutorial\n", + "Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array " + ] + }, + { + "cell_type": "code", + "execution_count": 82, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "######################################################\n", + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", + "seq1 = bb.Sequence()\n", + "\n", + "# We fill up the sequence by adding elements at different sequence positions.\n", + "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", + "\n", + "#\n", + "# Make blueprints, make elements\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"ramp\", (-0.0, 100e-9), 1\n", + ") # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"sine\", (-0.0, 100e-9), 2\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", + "\n", + "\n", + "# create elements\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "#\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(3, bp_boxes)\n", + "elem2.addBluePrint(1, bp_sineandboxes)\n", + "\n", + "# Fill up the sequence\n", + "seq1.addElement(1, elem1) # Call signature: seq. pos., element\n", + "seq1.addElement(2, elem2)\n", + "seq1.addElement(3, elem1)\n", + "\n", + "# set its sample rate\n", + "seq1.setSR(elem1.SR)\n", + "\n", + "seq1.setChannelAmplitude(1, 10e-3) # Call signature: channel, amplitude (peak-to-peak)\n", + "seq1.setChannelOffset(1, 0)\n", + "seq1.setChannelAmplitude(3, 10e-3)\n", + "seq1.setChannelOffset(3, 0)\n", + "\n", + "# Here we repeat each element twice and then proceed to the next, wrapping over at the end\n", + "seq1.setSequencingTriggerWait(1, 0)\n", + "seq1.setSequencingNumberOfRepetitions(1, 2)\n", + "seq1.setSequencingEventJumpTarget(1, 0)\n", + "seq1.setSequencingGoto(1, 2)\n", + "#\n", + "seq1.setSequencingTriggerWait(2, 0)\n", + "seq1.setSequencingNumberOfRepetitions(2, 2)\n", + "seq1.setSequencingEventJumpTarget(2, 0)\n", + "seq1.setSequencingGoto(2, 3)\n", + "#\n", + "seq1.setSequencingTriggerWait(3, 0)\n", + "seq1.setSequencingNumberOfRepetitions(3, 2)\n", + "seq1.setSequencingEventJumpTarget(3, 0)\n", + "seq1.setSequencingGoto(3, 1)\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Writing the Sequence to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 83, + "metadata": {}, + "outputs": [], + "source": [ + "seq1.write_to_json(\"testdata.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 84, + "metadata": {}, + "outputs": [], + "source": [ + "bp_boxes.write_to_json(\"blue.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reading the Sequence back from the file " + ] + }, + { + "cell_type": "code", + "execution_count": 85, + "metadata": {}, + "outputs": [], + "source": [ + "seq = bb.Sequence.init_from_json(\"testdata.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 86, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotter(seq)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test if the Sequences are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(seq == seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Ekstras\n", + "Example of how read and write blueprint from/to Sequence json file if needed." + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "metadata": {}, + "outputs": [], + "source": [ + "def readblueprint(path: str, element: int = 1, channel: int = 1):\n", + " tempseq = bb.Sequence.init_from_json(path)\n", + " return tempseq.element(element)._data[channel][\"blueprint\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "metadata": {}, + "outputs": [], + "source": [ + "def writeblueprint(\n", + " blueprint, path: str, SR: float = 1e9, SeqAmp: float = 10e-3, SeqOffset: float = 0\n", + "): # -> None\n", + " if blueprint.SR is None:\n", + " blueprint.setSR(SR)\n", + " elemtmp = bb.Element()\n", + " seqtmp = bb.Sequence()\n", + " elemtmp.addBluePrint(1, blueprint)\n", + " seqtmp.addElement(1, elemtmp)\n", + " seqtmp.setSR(blueprint.SR)\n", + " seqtmp.setChannelAmplitude(1, SeqAmp)\n", + " seqtmp.setChannelOffset(1, 0)\n", + " seqtmp.write_to_json(path)" + ] + }, + { + "cell_type": "code", + "execution_count": 90, + "metadata": {}, + "outputs": [], + "source": [ + "writeblueprint(bp_boxes, \"boxes.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 91, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (mpl.ratio !== 1) {\n", + " fig.send_message('set_dpi_ratio', { dpi_ratio: mpl.ratio });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute('style', 'box-sizing: content-box;');\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box; position: absolute; left: 0; top: 0; z-index: 1;'\n", + " );\n", + "\n", + " var resizeObserver = new ResizeObserver(function (entries) {\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * mpl.ratio);\n", + " canvas.setAttribute('height', height * mpl.ratio);\n", + " }\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'width: ' + width + 'px; height: ' + height + 'px;'\n", + " );\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " resizeObserver.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband_canvas.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " rubberband_canvas.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " this.rubberband_canvas.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / mpl.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / mpl.ratio;\n", + " var x1 = msg['x1'] / mpl.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / mpl.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / mpl.ratio,\n", + " fig.canvas.height / mpl.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " var cursor = msg['cursor'];\n", + " switch (cursor) {\n", + " case 0:\n", + " cursor = 'pointer';\n", + " break;\n", + " case 1:\n", + " cursor = 'default';\n", + " break;\n", + " case 2:\n", + " cursor = 'crosshair';\n", + " break;\n", + " case 3:\n", + " cursor = 'move';\n", + " break;\n", + " }\n", + " fig.rubberband_canvas.style.cursor = cursor;\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " evt.data.type = 'image/png';\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " evt.data\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "// from http://stackoverflow.com/questions/1114465/getting-mouse-location-in-canvas\n", + "mpl.findpos = function (e) {\n", + " //this section is from http://www.quirksmode.org/js/events_properties.html\n", + " var targ;\n", + " if (!e) {\n", + " e = window.event;\n", + " }\n", + " if (e.target) {\n", + " targ = e.target;\n", + " } else if (e.srcElement) {\n", + " targ = e.srcElement;\n", + " }\n", + " if (targ.nodeType === 3) {\n", + " // defeat Safari bug\n", + " targ = targ.parentNode;\n", + " }\n", + "\n", + " // pageX,Y are the mouse positions relative to the document\n", + " var boundingRect = targ.getBoundingClientRect();\n", + " var x = e.pageX - (boundingRect.left + document.body.scrollLeft);\n", + " var y = e.pageY - (boundingRect.top + document.body.scrollTop);\n", + "\n", + " return { x: x, y: y };\n", + "};\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * http://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " var canvas_pos = mpl.findpos(event);\n", + "\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " var x = canvas_pos.x * mpl.ratio;\n", + " var y = canvas_pos.y * mpl.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We want\n", + " * to control all of the cursor setting manually through the\n", + " * 'cursor' event from matplotlib */\n", + " event.preventDefault();\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.which === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.which;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.which !== 17) {\n", + " value += 'ctrl+';\n", + " }\n", + " if (event.altKey && event.which !== 18) {\n", + " value += 'alt+';\n", + " }\n", + " if (event.shiftKey && event.which !== 16) {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k';\n", + " value += event.which.toString();\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home icon-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left icon-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right icon-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows icon-move\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-square-o icon-check-empty\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o icon-save\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(msg['content']['data']);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / mpl.ratio;\n", + " fig.root.removeEventListener('remove', this._remove_fig_handler);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / mpl.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function () {\n", + " this.close_ws(this, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + " el.addEventListener('remove', this._remove_fig_handler);\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager) {\n", + " manager = IPython.keyboard_manager;\n", + " }\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bp_one_two = readblueprint(\"testdata.json\", element=2, channel=1)\n", + "plotter(bp_one_two)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.7" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/_sources/examples/Filter_compensation.ipynb.txt b/_sources/examples/Filter_compensation.ipynb.txt new file mode 100644 index 000000000..31411502a --- /dev/null +++ b/_sources/examples/Filter_compensation.ipynb.txt @@ -0,0 +1,3483 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Filter compensation with the ripasso module\n", + "\n", + "The broadbean module gets a helping hand from the ripasso module when it needs to compensate for transfer functions of transmission lines.\n", + "\n", + "The ripasso module lets us apply filters and their inverses to signals.\n", + "\n", + "The module has two built-in types of filter, namely an RC-circuit high-pass and an RC-circuit low-pass. The transfer functions are\n", + "\n", + "$$\n", + "H_n(f)=\\left(\\frac{2\\pi f\\mathrm{i}\\tau}{1+2\\pi f\\mathrm{i}\\tau }\\right)^{n},\n", + "$$\n", + "for the nth order high-pass and\n", + "$$\n", + "L_n(f)=\\left(\\frac{1}{1+2\\pi f\\mathrm{i}\\tau }\\right)^{n},\n", + "$$\n", + "for the nth order low-pass. The parameter $\\tau$ is one over the cut-off frequency.\n", + "\n", + "The inversion (filter compensation) is performed by mulitplying with the inverse transfer function in frequency space and transforming back to the time domain, e.g. for a given signal $s(t)$ and a high-pass filter of order n,\n", + "\n", + "$$\n", + " s_\\text{filtered}(t) = \\mathcal{F}^{-1}[\\mathcal{F}[s](f)\\cdot H_n(f)](t)\n", + "$$\n", + "and\n", + "$$\n", + " s_\\text{compensated}(t) = \\mathcal{F}^{-1}[\\mathcal{F}[s](f)\\cdot H_{-n}(f)](t).\n", + "$$\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "from broadbean.ripasso import (\n", + " applyCustomTransferFunction,\n", + " applyInverseRCFilter,\n", + " applyRCFilter,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "def squarewave(npts, periods=5):\n", + " periods = int(periods)\n", + " array = np.zeros(npts)\n", + "\n", + " for n in range(periods):\n", + " array[int(n * npts / periods) : int((2 * n + 1) * npts / 2 / periods)] = 1\n", + "\n", + " return array" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## RC Filters" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# Let's make some signals!\n", + "\n", + "SR = int(10e3)\n", + "npts = int(10e3)\n", + "\n", + "f_cut = 12 # filter cut-off frequency (Hz)\n", + "\n", + "time = np.linspace(0, npts / SR, npts)\n", + "T = time[-1]\n", + "\n", + "signal1 = np.sin(2 * np.pi * 5 / T * time) + 0.7 * np.cos(2 * np.pi * 1 / T * time)\n", + "signal2 = squarewave(npts, periods=4)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Then we may high-pass them or low-pass them\n", + "\n", + "filtertype = \"HP\"\n", + "# filtertype = 'LP'\n", + "\n", + "signal1_filt = applyRCFilter(signal1, SR, filtertype, f_cut, order=1)\n", + "signal2_filt = applyRCFilter(signal2, SR, filtertype, f_cut, order=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axs = plt.subplots(2, 1)\n", + "axs[0].plot(time, signal1, label=\"Input signal\")\n", + "axs[0].plot(time, signal1_comp, label=\"Compensated\")\n", + "axs[0].set_xlabel(\"Time (s)\")\n", + "axs[0].set_ylabel(\"Signal (a.u.)\")\n", + "axs[0].legend()\n", + "\n", + "axs[1].plot(time, signal2, label=\"Input signal\")\n", + "axs[1].plot(time, signal2_comp, label=\"Compensated\")\n", + "axs[1].set_xlabel(\"Time (s)\")\n", + "axs[1].set_ylabel(\"Signal (a.u.)\")\n", + "axs[1].legend()\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Lets first make some semi-realistic transfer function\n", + "\n", + "tf_points = 500\n", + "\n", + "tf_freq = np.linspace(0, 1000, tf_points)\n", + "tf_amp = np.zeros(tf_points)\n", + "tf_amp += np.hanning(2 * tf_points)[tf_points:][::-1] # A high-pass\n", + "tf_amp += (\n", + " 0.1 # Note: a transfer function with very low values give unphysical compensation\n", + ")\n", + "tf_amp /= tf_amp.max()\n", + "\n", + "# and some experimental noise\n", + "tf_noise_amp = 0.02\n", + "tf_amp += (\n", + " np.convolve(0.02 * np.random.randn(tf_points), np.array([0.5, 1, 0.5]), mode=\"same\")\n", + " / 2\n", + ")\n", + "\n", + "# And then our favourite signal: the square wave\n", + "\n", + "sig_points = 1000\n", + "signal = squarewave(sig_points, periods=10) + 0.01 * np.random.randn(1000)\n", + "SR = 2000 # We pretend that the signal was sampled with this sample rate\n", + "time = np.linspace(0, sig_points / SR, sig_points)\n", + "\n", + "# Then we may apply the filter we made\n", + "\n", + "signal_filtered = applyCustomTransferFunction(signal, SR, tf_freq, tf_amp)\n", + "\n", + "# Or conversely, we may apply its inverse\n", + "\n", + "signal_compensated = applyCustomTransferFunction(\n", + " signal, SR, tf_freq, tf_amp, invert=True\n", + ")\n", + "\n", + "# Finally, it's nice to visualise things\n", + "\n", + "fig, axs = plt.subplots(2, 2)\n", + "axs[0, 0].plot(tf_freq, tf_amp)\n", + "axs[0, 0].set_xlabel(\"Freq. (Hz)\")\n", + "axs[0, 0].set_ylabel(\"Transfer func.\")\n", + "\n", + "axs[0, 1].plot(time, signal, color=\"#ff9900\")\n", + "axs[0, 1].set_xlabel(\"Time (s)\")\n", + "axs[0, 1].set_ylabel(\"Sig. ampl. (arb. un.)\")\n", + "axs[0, 1].set_title(\"Input\")\n", + "\n", + "axs[1, 0].plot(time, signal_filtered, color=\"#339966\")\n", + "axs[1, 0].set_xlabel(\"Time (s)\")\n", + "axs[1, 0].set_ylabel(\"Sig. ampl. (arb. un.)\")\n", + "axs[1, 0].set_title(\"Filtered\")\n", + "\n", + "axs[1, 1].plot(time, signal_compensated, color=\"#990033\")\n", + "axs[1, 1].set_xlabel(\"Time (s)\")\n", + "axs[1, 1].set_ylabel(\"Sig.ampl. (arb. un.)\")\n", + "axs[1, 1].set_title(\"Compensated\")\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.6.0" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/_sources/examples/Making_output_for_Tektronix_AWG70000A.ipynb.txt b/_sources/examples/Making_output_for_Tektronix_AWG70000A.ipynb.txt new file mode 100644 index 000000000..6fe524d0a --- /dev/null +++ b/_sources/examples/Making_output_for_Tektronix_AWG70000A.ipynb.txt @@ -0,0 +1,1075 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "The Tektronix AWG70000A series internally use `.seqx` files. The QCoDeS driver for the AWGs knows how to compile these files and broadbean knows what QCoDeS need to do so. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 1: Make a sequence" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# We make a sequence consisting of a bsic element with:\n", + "# a wait time, a ramp up, a sine, a ramp down, and more wait time\n", + "# And then we vary the sine frequency\n", + "%matplotlib notebook\n", + "import numpy as np\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "sine = bb.PulseAtoms.sine\n", + "ramp = bb.PulseAtoms.ramp\n", + "\n", + "##########################\n", + "# settings\n", + "\n", + "SR = 25e9\n", + "t1 = 10e-9 # first wait time (s)\n", + "ramp_time = 5e-9 # the ramp time (s)\n", + "t_sine = 25e-9 # the sine time (s)\n", + "high_level = 0.1 # the high level (V)\n", + "sine_amp = 0.05 # the sine amplitude (V)\n", + "t2 = 150e-9 # the second wait time (s)\n", + "f0 = 1e9 # the base frequency of the sine (Hz)\n", + "\n", + "baseshape = bb.BluePrint()\n", + "baseshape.insertSegment(0, ramp, (0, 0), dur=t1)\n", + "baseshape.insertSegment(1, ramp, (0, high_level), dur=ramp_time)\n", + "baseshape.insertSegment(\n", + " 2, sine, (f0, sine_amp, high_level, 0), dur=t_sine, name=\"drive\"\n", + ")\n", + "baseshape.insertSegment(3, ramp, (high_level, 0), dur=ramp_time)\n", + "baseshape.insertSegment(4, ramp, (0, 0), dur=t2, name=\"wait\")\n", + "baseshape.setSegmentMarker(\"wait\", (0, t_sine), 1)\n", + "baseshape.setSegmentMarker(\"drive\", (0, t_sine), 2)\n", + "baseshape.setSR(SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Absolute time marker specification\n", + "\n", + "# The blueprint has a list of tuples for each marker. The tuples are (switch_on_time, duration)\n", + "\n", + "# create a blueprint\n", + "bp_atm = bb.BluePrint()\n", + "bp_atm.setSR(100)\n", + "bp_atm.insertSegment(0, ramp, (0, 1), dur=3)\n", + "bp_atm.insertSegment(1, sine, (0.5, 1, 1, 0), dur=2)\n", + "bp_atm.insertSegment(2, ramp, (1, 0), dur=3)\n", + "\n", + "# specify markers in absolute time\n", + "bp_atm.marker1 = [(1, 0.5), (2, 0.5)]\n", + "bp_atm.marker2 = [(1.5, 0.2), (2.5, 0.1)]\n", + "\n", + "plotter(bp_atm)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Relative time marker specification\n", + "\n", + "bp_rtm = bb.BluePrint()\n", + "bp_rtm.setSR(100)\n", + "bp_rtm.insertSegment(0, ramp, (0, 1), dur=1)\n", + "bp_rtm.insertSegment(1, ramp, (1, 1), dur=1)\n", + "bp_rtm.insertSegment(\n", + " 2, sine, (1.675, 1, 0, np.pi / 2), dur=1.5, name=\"mysine\"\n", + ") # This is the important segment\n", + "# make marker 1 go ON a bit before the sine comes on\n", + "bp_rtm.setSegmentMarker(\n", + " \"mysine\", (-0.1, 0.2), 1\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "bp_rtm.setSegmentMarker(\"mysine\", (0.75, 0.1), 2)\n", + "\n", + "plotter(bp_rtm)\n", + "\n", + "# Even if we insert segments before and after the sine, the markers \"stick\" to the sine segment\n", + "bp_rtm.insertSegment(0, ramp, (0, 0), dur=1)\n", + "bp_rtm.insertSegment(-1, ramp, (0, 0.2), dur=1)\n", + "\n", + "plotter(bp_rtm)\n", + "\n", + "\n", + "# NB: the two different ways of inputting markers will never directly conflict, since one only specifies when to turn\n", + "# markers ON. It is up to the user to ensure that markers switch off again as expected, i.e. that different marker\n", + "# specifications do not overlap." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modifying blueprints\n", + "([back to ToC](#Table-of-Contents))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 1\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=0.1e-6)\n", + "bp_square.insertSegment(1, ramp, (10e-3, 10e-3), name=\"top\", dur=0.1e-6)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=0.1e-6)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 25e-3, 0, 0), dur=0.3e-6)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "# Now we create an element and add the blueprints to channel 1 and 3, respectively\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "\n", + "# We can check the validity of the element\n", + "elem1.validateDurations() # raises an ElementDurationError if something is wrong. If all is OK, does nothing.\n", + "\n", + "# And we can plot the element\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Designated channels: [1, 3]\n", + "Total duration: 6e-07 s\n", + "Sample rate: 1000000000.0 (Sa/S)\n" + ] + } + ], + "source": [ + "# An element has several features\n", + "print(f\"Designated channels: {elem1.channels}\")\n", + "print(f\"Total duration: {elem1.duration} s\")\n", + "print(f\"Sample rate: {elem1.SR} (Sa/S)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The sequence can be validated\n", + "seq1.checkConsistency() # returns True if all is well, raises errors if not\n", + "\n", + "# And the sequence can (if valid) be plotted\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tektronix AWG 5014 output\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "The sequence object can output a tuple matching the call signature of the QCoDeS Tektronix AWG 5014 driver.\n", + "\n", + "For the translation from voltage to AWG unsigned integer format to be correct, the voltage ranges and offsets of the AWG channels must be specified (NB: This will **NOT** work if the channels on the AWG are in high/low mode).\n", + "\n", + "Furthermore, the AWG sequencer options should be specified for each sequence element." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "scrolled": false + }, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# For sanity checking, it may be helpful to see how the compensated waveforms look.\n", + "# The plotter function can display the delays and filter compensations\n", + "\n", + "plotter(seq1, apply_filters=True, apply_delays=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sequences varying parameters\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "The module contains a few wrapper functions to easily generate sequences with some parameter(s) varying throughout the sequence. First two examples where a Base element is provided and varied, then an example where an existing Sequence is repeated." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 1: vary the duration of a square pulse\n", + "\n", + "# First, we make a basic element, in this example containing just a single blueprint\n", + "basebp = bb.BluePrint()\n", + "basebp.insertSegment(0, ramp, (0, 0), dur=0.5)\n", + "basebp.insertSegment(1, ramp, (1, 1), dur=1, name=\"varyme\")\n", + "basebp.insertSegment(2, \"waituntil\", 5)\n", + "basebp.setSR(100)\n", + "\n", + "baseelem = bb.Element()\n", + "baseelem.addBluePrint(1, basebp)\n", + "\n", + "plotter(baseelem)\n", + "\n", + "# Now we make a 5-step sequence varying the duration of the high level\n", + "# The inputs are lists, since several things can be varied (see Example 2)\n", + "channels = [1]\n", + "names = [\"varyme\"]\n", + "args = [\"duration\"]\n", + "iters = [[1, 1.5, 2, 2.5, 3]]\n", + "\n", + "seq1 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n button.click(method_name, toolbar_event);\n button.mouseover(tooltip, toolbar_mouse_event);\n nav_element.append(button);\n }\n\n // Add the status bar.\n var status_bar = $('');\n nav_element.append(status_bar);\n this.message = status_bar[0];\n\n // Add the close button to the window.\n var buttongrp = $('
');\n var button = $('');\n button.click(function (evt) { fig.handle_close(fig, {}); } );\n button.mouseover('Stop Interaction', toolbar_mouse_event);\n buttongrp.append(button);\n var titlebar = this.root.find($('.ui-dialog-titlebar'));\n titlebar.prepend(buttongrp);\n}\n\nmpl.figure.prototype._root_extra_style = function(el){\n var fig = this\n el.on(\"remove\", function(){\n\tfig.close_ws(fig, {});\n });\n}\n\nmpl.figure.prototype._canvas_extra_style = function(el){\n // this is important to make the div 'focusable\n el.attr('tabindex', 0)\n // reach out to IPython and tell the keyboard manager to turn it's self\n // off when our div gets focus\n\n // location in version 3\n if (IPython.notebook.keyboard_manager) {\n IPython.notebook.keyboard_manager.register_events(el);\n }\n else {\n // location in version 2\n IPython.keyboard_manager.register_events(el);\n }\n\n}\n\nmpl.figure.prototype._key_event_extra = function(event, name) {\n var manager = IPython.notebook.keyboard_manager;\n if (!manager)\n manager = IPython.keyboard_manager;\n\n // Check for shift+enter\n if (event.shiftKey && event.which == 13) {\n this.canvas_div.blur();\n // select the cell after this one\n var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n IPython.notebook.select(index + 1);\n }\n}\n\nmpl.figure.prototype.handle_save = function(fig, msg) {\n fig.ondownload(fig, null);\n}\n\n\nmpl.find_output_cell = function(html_output) {\n // Return the cell and output element which can be found *uniquely* in the notebook.\n // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n // IPython event is triggered only after the cells have been serialised, which for\n // our purposes (turning an active figure into a static one), is too late.\n var cells = IPython.notebook.get_cells();\n var ncells = cells.length;\n for (var i=0; i= 3 moved mimebundle to data attribute of output\n data = data.data;\n }\n if (data['text/html'] == html_output) {\n return [cell, data, j];\n }\n }\n }\n }\n}\n\n// Register the function which deals with the matplotlib target/channel.\n// The kernel may be null if the page has been refreshed.\nif (IPython.notebook.kernel != null) {\n IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n}\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/william/.pyenv/versions/3.6.0/envs/qcodesdevel/lib/python3.6/site-packages/matplotlib/pyplot.py:524: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (`matplotlib.pyplot.figure`) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam `figure.max_open_warning`).\n", + " max_open_warning, RuntimeWarning)\n" + ] + }, + { + "data": { + "application/javascript": "/* Put everything inside the global mpl namespace */\nwindow.mpl = {};\n\n\nmpl.get_websocket_type = function() {\n if (typeof(WebSocket) !== 'undefined') {\n return WebSocket;\n } else if (typeof(MozWebSocket) !== 'undefined') {\n return MozWebSocket;\n } else {\n alert('Your browser does not have WebSocket support.' +\n 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n 'Firefox 4 and 5 are also supported but you ' +\n 'have to enable WebSockets in about:config.');\n };\n}\n\nmpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n this.id = figure_id;\n\n this.ws = websocket;\n\n this.supports_binary = (this.ws.binaryType != undefined);\n\n if (!this.supports_binary) {\n var warnings = document.getElementById(\"mpl-warnings\");\n if (warnings) {\n warnings.style.display = 'block';\n warnings.textContent = (\n \"This browser does not support binary websocket messages. \" +\n \"Performance may be slow.\");\n }\n }\n\n this.imageObj = new Image();\n\n this.context = undefined;\n this.message = undefined;\n this.canvas = undefined;\n this.rubberband_canvas = undefined;\n this.rubberband_context = undefined;\n this.format_dropdown = undefined;\n\n this.image_mode = 'full';\n\n this.root = $('
');\n this._root_extra_style(this.root)\n this.root.attr('style', 'display: inline-block');\n\n $(parent_element).append(this.root);\n\n this._init_header(this);\n this._init_canvas(this);\n this._init_toolbar(this);\n\n var fig = this;\n\n this.waiting = false;\n\n this.ws.onopen = function () {\n fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n fig.send_message(\"send_image_mode\", {});\n if (mpl.ratio != 1) {\n fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n }\n fig.send_message(\"refresh\", {});\n }\n\n this.imageObj.onload = function() {\n if (fig.image_mode == 'full') {\n // Full images could contain transparency (where diff images\n // almost always do), so we need to clear the canvas so that\n // there is no ghosting.\n fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n }\n fig.context.drawImage(fig.imageObj, 0, 0);\n };\n\n this.imageObj.onunload = function() {\n this.ws.close();\n }\n\n this.ws.onmessage = this._make_on_message_function(this);\n\n this.ondownload = ondownload;\n}\n\nmpl.figure.prototype._init_header = function() {\n var titlebar = $(\n '
');\n var titletext = $(\n '
');\n titlebar.append(titletext)\n this.root.append(titlebar);\n this.header = titletext[0];\n}\n\n\n\nmpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n\n}\n\n\nmpl.figure.prototype._root_extra_style = function(canvas_div) {\n\n}\n\nmpl.figure.prototype._init_canvas = function() {\n var fig = this;\n\n var canvas_div = $('
');\n\n canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n\n function canvas_keyboard_event(event) {\n return fig.key_event(event, event['data']);\n }\n\n canvas_div.keydown('key_press', canvas_keyboard_event);\n canvas_div.keyup('key_release', canvas_keyboard_event);\n this.canvas_div = canvas_div\n this._canvas_extra_style(canvas_div)\n this.root.append(canvas_div);\n\n var canvas = $('');\n canvas.addClass('mpl-canvas');\n canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n\n this.canvas = canvas[0];\n this.context = canvas[0].getContext(\"2d\");\n\n var backingStore = this.context.backingStorePixelRatio ||\n\tthis.context.webkitBackingStorePixelRatio ||\n\tthis.context.mozBackingStorePixelRatio ||\n\tthis.context.msBackingStorePixelRatio ||\n\tthis.context.oBackingStorePixelRatio ||\n\tthis.context.backingStorePixelRatio || 1;\n\n mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n\n var rubberband = $('');\n rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n\n var pass_mouse_events = true;\n\n canvas_div.resizable({\n start: function(event, ui) {\n pass_mouse_events = false;\n },\n resize: function(event, ui) {\n fig.request_resize(ui.size.width, ui.size.height);\n },\n stop: function(event, ui) {\n pass_mouse_events = true;\n fig.request_resize(ui.size.width, ui.size.height);\n },\n });\n\n function mouse_event_fn(event) {\n if (pass_mouse_events)\n return fig.mouse_event(event, event['data']);\n }\n\n rubberband.mousedown('button_press', mouse_event_fn);\n rubberband.mouseup('button_release', mouse_event_fn);\n // Throttle sequential mouse events to 1 every 20ms.\n rubberband.mousemove('motion_notify', mouse_event_fn);\n\n rubberband.mouseenter('figure_enter', mouse_event_fn);\n rubberband.mouseleave('figure_leave', mouse_event_fn);\n\n canvas_div.on(\"wheel\", function (event) {\n event = event.originalEvent;\n event['data'] = 'scroll'\n if (event.deltaY < 0) {\n event.step = 1;\n } else {\n event.step = -1;\n }\n mouse_event_fn(event);\n });\n\n canvas_div.append(canvas);\n canvas_div.append(rubberband);\n\n this.rubberband = rubberband;\n this.rubberband_canvas = rubberband[0];\n this.rubberband_context = rubberband[0].getContext(\"2d\");\n this.rubberband_context.strokeStyle = \"#000000\";\n\n this._resize_canvas = function(width, height) {\n // Keep the size of the canvas, canvas container, and rubber band\n // canvas in synch.\n canvas_div.css('width', width)\n canvas_div.css('height', height)\n\n canvas.attr('width', width * mpl.ratio);\n canvas.attr('height', height * mpl.ratio);\n canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n\n rubberband.attr('width', width);\n rubberband.attr('height', height);\n }\n\n // Set the figure to an initial 600x600px, this will subsequently be updated\n // upon first draw.\n this._resize_canvas(600, 600);\n\n // Disable right mouse context menu.\n $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n return false;\n });\n\n function set_focus () {\n canvas.focus();\n canvas_div.focus();\n }\n\n window.setTimeout(set_focus, 100);\n}\n\nmpl.figure.prototype._init_toolbar = function() {\n var fig = this;\n\n var nav_element = $('
')\n nav_element.attr('style', 'width: 100%');\n this.root.append(nav_element);\n\n // Define a callback function for later on.\n function toolbar_event(event) {\n return fig.toolbar_button_onclick(event['data']);\n }\n function toolbar_mouse_event(event) {\n return fig.toolbar_button_onmouseover(event['data']);\n }\n\n for(var toolbar_ind in mpl.toolbar_items) {\n var name = mpl.toolbar_items[toolbar_ind][0];\n var tooltip = mpl.toolbar_items[toolbar_ind][1];\n var image = mpl.toolbar_items[toolbar_ind][2];\n var method_name = mpl.toolbar_items[toolbar_ind][3];\n\n if (!name) {\n // put a spacer in here.\n continue;\n }\n var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotter(bp1)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "# Now make a variation of the height\n", + "\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp1)\n", + "\n", + "elem2 = elem1.copy()\n", + "elem2.changeArg(1, \"perturbation\", \"start\", 0.75)\n", + "elem2.changeArg(1, \"perturbation\", \"stop\", 0.75)\n", + "\n", + "elem3 = elem1.copy()\n", + "elem3.changeArg(1, \"perturbation\", \"start\", 0.5)\n", + "elem3.changeArg(1, \"perturbation\", \"stop\", 0.5)\n", + "\n", + "# And put that together in a sequence\n", + "seq = bb.Sequence()\n", + "seq.addElement(1, elem1)\n", + "seq.addElement(2, elem2)\n", + "seq.addElement(3, elem3)\n", + "seq.setSR(SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $('');\n", + " button.click(method_name, toolbar_event);\n", + " button.mouseover(tooltip, toolbar_mouse_event);\n", + " nav_element.append(button);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = $('');\n", + " nav_element.append(status_bar);\n", + " this.message = status_bar[0];\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = $('
');\n", + " var button = $('');\n", + " button.click(function (evt) { fig.handle_close(fig, {}); } );\n", + " button.mouseover('Stop Interaction', toolbar_mouse_event);\n", + " buttongrp.append(button);\n", + " var titlebar = this.root.find($('.ui-dialog-titlebar'));\n", + " titlebar.prepend(buttongrp);\n", + "}\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(el){\n", + " var fig = this\n", + " el.on(\"remove\", function(){\n", + "\tfig.close_ws(fig, {});\n", + " });\n", + "}\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(el){\n", + " // this is important to make the div 'focusable\n", + " el.attr('tabindex', 0)\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " }\n", + " else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._key_event_extra = function(event, name) {\n", + " var manager = IPython.notebook.keyboard_manager;\n", + " if (!manager)\n", + " manager = IPython.keyboard_manager;\n", + "\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which == 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "}\n", + "\n", + "mpl.figure.prototype.handle_save = function(fig, msg) {\n", + " fig.ondownload(fig, null);\n", + "}\n", + "\n", + "\n", + "mpl.find_output_cell = function(html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i=0; i= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] == html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "}\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel != null) {\n", + " IPython.notebook.kernel.comm_manager.register_target('matplotlib', mpl.mpl_figure_comm);\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Let's make a sequence instead of an element\n", + "\n", + "SR = 1e9\n", + "t1 = 200e-6 # wait\n", + "t2 = 20e-9 # perturb the system\n", + "t3 = 250e-6 # read out\n", + "\n", + "compression = 100 # this number has to be chosen with some care\n", + "\n", + "bp1 = bb.BluePrint()\n", + "bp1.insertSegment(0, ramp, (0, 0), dur=t1 / compression)\n", + "bp1.setSR(SR)\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp1)\n", + "#\n", + "bp2 = bb.BluePrint()\n", + "bp2.insertSegment(0, ramp, (1, 1), dur=t2, name=\"perturbation\")\n", + "bp2.setSR(SR)\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(1, bp2)\n", + "#\n", + "bp3 = bb.BluePrint()\n", + "bp3.insertSegment(0, ramp, (0, 0), dur=t3 / compression)\n", + "bp3.setSR(SR)\n", + "elem3 = bb.Element()\n", + "elem3.addBluePrint(1, bp3)\n", + "\n", + "seq = bb.Sequence()\n", + "seq.addElement(1, elem1)\n", + "seq.setSequencingNumberOfRepetitions(1, compression)\n", + "seq.addElement(2, elem2)\n", + "seq.addElement(3, elem3)\n", + "seq.setSequencingNumberOfRepetitions(3, compression)\n", + "seq.setSR(SR)\n", + "\n", + "# Now make the variation\n", + "seq2 = seq.copy()\n", + "seq2.element(2).changeArg(1, \"perturbation\", \"start\", 0.75)\n", + "seq2.element(2).changeArg(1, \"perturbation\", \"stop\", 0.75)\n", + "#\n", + "seq3 = seq.copy()\n", + "seq3.element(2).changeArg(1, \"perturbation\", \"start\", 0.5)\n", + "seq3.element(2).changeArg(1, \"perturbation\", \"stop\", 0.5)\n", + "#\n", + "fullseq = seq + seq2 + seq3\n", + "plotter(fullseq)" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "13560" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The above sequence achieves the same as the uncompresed, but has fewer points\n", + "fullseq.points" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Now using subsequences\n", + "\n", + "Subsequences come into play when we want to, say, repeat each wait-perturb-wait element 25 times.\n", + "In the uncompressed case, that can only be achieved by adding each element 24 times more, thus resulting in a very large output file. Using subsequences, we can get away with a much smaller file size." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "window.mpl = {};\n", + "\n", + "\n", + "mpl.get_websocket_type = function() {\n", + " if (typeof(WebSocket) !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof(MozWebSocket) !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert('Your browser does not have WebSocket support.' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.');\n", + " };\n", + "}\n", + "\n", + "mpl.figure = function(figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = (this.ws.binaryType != undefined);\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById(\"mpl-warnings\");\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent = (\n", + " \"This browser does not support binary websocket messages. \" +\n", + " \"Performance may be slow.\");\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = $('
');\n", + " this._root_extra_style(this.root)\n", + " this.root.attr('style', 'display: inline-block');\n", + "\n", + " $(parent_element).append(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message(\"supports_binary\", {value: fig.supports_binary});\n", + " fig.send_message(\"send_image_mode\", {});\n", + " if (mpl.ratio != 1) {\n", + " fig.send_message(\"set_dpi_ratio\", {'dpi_ratio': mpl.ratio});\n", + " }\n", + " fig.send_message(\"refresh\", {});\n", + " }\n", + "\n", + " this.imageObj.onload = function() {\n", + " if (fig.image_mode == 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function() {\n", + " this.ws.close();\n", + " }\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "}\n", + "\n", + "mpl.figure.prototype._init_header = function() {\n", + " var titlebar = $(\n", + " '
');\n", + " var titletext = $(\n", + " '
');\n", + " titlebar.append(titletext)\n", + " this.root.append(titlebar);\n", + " this.header = titletext[0];\n", + "}\n", + "\n", + "\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "\n", + "mpl.figure.prototype._root_extra_style = function(canvas_div) {\n", + "\n", + "}\n", + "\n", + "mpl.figure.prototype._init_canvas = function() {\n", + " var fig = this;\n", + "\n", + " var canvas_div = $('
');\n", + "\n", + " canvas_div.attr('style', 'position: relative; clear: both; outline: 0');\n", + "\n", + " function canvas_keyboard_event(event) {\n", + " return fig.key_event(event, event['data']);\n", + " }\n", + "\n", + " canvas_div.keydown('key_press', canvas_keyboard_event);\n", + " canvas_div.keyup('key_release', canvas_keyboard_event);\n", + " this.canvas_div = canvas_div\n", + " this._canvas_extra_style(canvas_div)\n", + " this.root.append(canvas_div);\n", + "\n", + " var canvas = $('');\n", + " canvas.addClass('mpl-canvas');\n", + " canvas.attr('style', \"left: 0; top: 0; z-index: 0; outline: 0\")\n", + "\n", + " this.canvas = canvas[0];\n", + " this.context = canvas[0].getContext(\"2d\");\n", + "\n", + " var backingStore = this.context.backingStorePixelRatio ||\n", + "\tthis.context.webkitBackingStorePixelRatio ||\n", + "\tthis.context.mozBackingStorePixelRatio ||\n", + "\tthis.context.msBackingStorePixelRatio ||\n", + "\tthis.context.oBackingStorePixelRatio ||\n", + "\tthis.context.backingStorePixelRatio || 1;\n", + "\n", + " mpl.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband = $('');\n", + " rubberband.attr('style', \"position: absolute; left: 0; top: 0; z-index: 1;\")\n", + "\n", + " var pass_mouse_events = true;\n", + "\n", + " canvas_div.resizable({\n", + " start: function(event, ui) {\n", + " pass_mouse_events = false;\n", + " },\n", + " resize: function(event, ui) {\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " stop: function(event, ui) {\n", + " pass_mouse_events = true;\n", + " fig.request_resize(ui.size.width, ui.size.height);\n", + " },\n", + " });\n", + "\n", + " function mouse_event_fn(event) {\n", + " if (pass_mouse_events)\n", + " return fig.mouse_event(event, event['data']);\n", + " }\n", + "\n", + " rubberband.mousedown('button_press', mouse_event_fn);\n", + " rubberband.mouseup('button_release', mouse_event_fn);\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " rubberband.mousemove('motion_notify', mouse_event_fn);\n", + "\n", + " rubberband.mouseenter('figure_enter', mouse_event_fn);\n", + " rubberband.mouseleave('figure_leave', mouse_event_fn);\n", + "\n", + " canvas_div.on(\"wheel\", function (event) {\n", + " event = event.originalEvent;\n", + " event['data'] = 'scroll'\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " mouse_event_fn(event);\n", + " });\n", + "\n", + " canvas_div.append(canvas);\n", + " canvas_div.append(rubberband);\n", + "\n", + " this.rubberband = rubberband;\n", + " this.rubberband_canvas = rubberband[0];\n", + " this.rubberband_context = rubberband[0].getContext(\"2d\");\n", + " this.rubberband_context.strokeStyle = \"#000000\";\n", + "\n", + " this._resize_canvas = function(width, height) {\n", + " // Keep the size of the canvas, canvas container, and rubber band\n", + " // canvas in synch.\n", + " canvas_div.css('width', width)\n", + " canvas_div.css('height', height)\n", + "\n", + " canvas.attr('width', width * mpl.ratio);\n", + " canvas.attr('height', height * mpl.ratio);\n", + " canvas.attr('style', 'width: ' + width + 'px; height: ' + height + 'px;');\n", + "\n", + " rubberband.attr('width', width);\n", + " rubberband.attr('height', height);\n", + " }\n", + "\n", + " // Set the figure to an initial 600x600px, this will subsequently be updated\n", + " // upon first draw.\n", + " this._resize_canvas(600, 600);\n", + "\n", + " // Disable right mouse context menu.\n", + " $(this.rubberband_canvas).bind(\"contextmenu\",function(e){\n", + " return false;\n", + " });\n", + "\n", + " function set_focus () {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "}\n", + "\n", + "mpl.figure.prototype._init_toolbar = function() {\n", + " var fig = this;\n", + "\n", + " var nav_element = $('
')\n", + " nav_element.attr('style', 'width: 100%');\n", + " this.root.append(nav_element);\n", + "\n", + " // Define a callback function for later on.\n", + " function toolbar_event(event) {\n", + " return fig.toolbar_button_onclick(event['data']);\n", + " }\n", + " function toolbar_mouse_event(event) {\n", + " return fig.toolbar_button_onmouseover(event['data']);\n", + " }\n", + "\n", + " for(var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " // put a spacer in here.\n", + " continue;\n", + " }\n", + " var button = $(' +
+ +
+ + +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

broadbean package

+
+

Submodules

+
+
+

broadbean.blueprint module

+
+
+class broadbean.blueprint.BluePrint(funlist=None, argslist=None, namelist=None, marker1=None, marker2=None, segmentmarker1=None, segmentmarker2=None, SR=None, durslist=None)[source]
+

Bases: object

+

The class of a waveform to become.

+
+
+property SR
+

Sample rate of the blueprint

+
+ +
+
+classmethod blueprint_from_description(blue_dict)[source]
+

Returns a blueprint from a description given as a dict

+
+
Parameters:
+
    +
  • blue_dict – a dict in the same form as returned by

  • +
  • BluePrint.description

  • +
+
+
+
+ +
+
+changeArg(name, arg, value, replaceeverywhere=False)[source]
+

Change an argument of one or more of the functions in the blueprint.

+
+
Parameters:
+
    +
  • name (str) – The name of the segment in which to change an argument

  • +
  • arg (Union[int, str]) – Either the position (int) or name (str) of +the argument to change

  • +
  • value (Union[int, float]) – The new value of the argument

  • +
  • replaceeverywhere (bool) – If True, the same argument is overwritten +in ALL segments where the name matches. E.g. ‘gaussian1’ will +match ‘gaussian’, ‘gaussian2’, etc. If False, only the segment +with exact name match gets a replacement.

  • +
+
+
Raises:
+
    +
  • ValueError – If the argument can not be matched (either the argument + name does not match or the argument number is wrong).

  • +
  • ValueError – If the name can not be matched.

  • +
+
+
+
+ +
+
+changeDuration(name, dur, replaceeverywhere=False)[source]
+

Change the duration of one or more segments in the blueprint

+
+
Parameters:
+
    +
  • name (str) – The name of the segment in which to change duration

  • +
  • dur (Union[float, int]) – The new duration.

  • +
  • replaceeverywhere (Optional[bool]) – If True, the duration(s) +is(are) overwritten in ALL segments where the name matches. +E.g. ‘gaussian1’ will match ‘gaussian’, ‘gaussian2’, +etc. If False, only the segment with exact name match +gets a replacement.

  • +
+
+
Raises:
+
    +
  • ValueError – If durations are not specified for the blueprint

  • +
  • ValueError – If too many or too few durations are given.

  • +
  • ValueError – If no segment matches the name.

  • +
  • ValueError – If dur is not positive

  • +
  • ValueError – If SR is given for the blueprint and dur is less than + 1/SR.

  • +
+
+
+
+ +
+
+copy()[source]
+

Returns a copy of the BluePrint

+
+ +
+
+property description
+

Returns a dict describing the blueprint.

+
+ +
+
+property duration
+

The total duration of the BluePrint. If necessary, all the arrays +are built.

+
+ +
+
+property durations
+

The list of durations

+
+ +
+
+classmethod init_from_json(path_to_file: str) BluePrint[source]
+

Reads blueprint from JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to be read ex:

  • +
  • path_to_file/blueprint.json

  • +
  • write_to_json (by the function)

  • +
  • writen (The JSON file needs to be structured as if it was)

  • +
  • write_to_json

  • +
+
+
+
+ +
+
+insertSegment(pos, func, args=(), dur=None, name=None, durs=None)[source]
+

Insert a segment into the bluePrint.

+
+
Parameters:
+
    +
  • pos (int) – The position at which to add the segment. Counts like +a python list; 0 is first, -1 is last. Values below -1 are +not allowed, though.

  • +
  • func (function) – Function describing the segment. Must have its +duration as the last argument (unless its a special function).

  • +
  • args (Optional[Tuple[Any]]) – Tuple of arguments BESIDES duration. +Default: ()

  • +
  • dur (Optional[Union[int, float]]) – The duration of the +segment. Must be given UNLESS the segment is +‘waituntil’ or ‘ensureaverage_fixed_level’

  • +
  • Optional[str] (name) – Name of the segment. If none is given, +the segment will receive the name of its function, +possibly with a number appended.

  • +
+
+
Raises:
+
    +
  • ValueError – If the position is negative

  • +
  • ValueError – If the name ends in a number

  • +
+
+
+
+ +
+
+property length_segments
+

Returns the number of segments in the blueprint

+
+ +
+
+property points
+

The total number of points in the BluePrint. If necessary, +all the arrays are built.

+
+ +
+
+removeSegment(name)[source]
+

Remove the specified segment from the blueprint.

+
+
Parameters:
+

name (str) – The name of the segment to remove.

+
+
+
+ +
+
+removeSegmentMarker(name: str, markerID: int) None[source]
+

Remove all bound markers from a specific segment

+
+
Parameters:
+
    +
  • name (str) – Name of the segment

  • +
  • markerID (int) – Which marker channel to remove from (1 or 2).

  • +
  • number (int) – The number of the marker, in case several markers are +bound to one element. Default: 1 (the first marker).

  • +
+
+
+
+ +
+
+setSR(SR)[source]
+

Set the associated sample rate

+
+
Parameters:
+

SR (Union[int, float]) – The sample rate in Sa/s.

+
+
+
+ +
+
+setSegmentMarker(name, specs, markerID)[source]
+

Bind a marker to a specific segment.

+
+
Parameters:
+
    +
  • name (str) – Name of the segment

  • +
  • specs (tuple) – Marker specification tuple, (delay, duration), +where the delay is relative to the segment start

  • +
  • markerID (int) – Which marker channel to output on. Must be 1 or 2.

  • +
+
+
+
+ +
+
+showPrint()[source]
+

Pretty-print the contents of the BluePrint. Not finished.

+
+ +
+
+write_to_json(path_to_file: str) None[source]
+

Writes blueprint to JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to write to ex:

  • +
  • path_to_file/blueprint.json

  • +
+
+
+
+ +
+ +
+
+exception broadbean.blueprint.SegmentDurationError[source]
+

Bases: Exception

+
+ +
+
+

broadbean.broadbean module

+
+
+class broadbean.broadbean.PulseAtoms[source]
+

Bases: object

+

A class full of static methods. +The basic pulse shapes.

+

Any pulse shape function should return a list or an np.array +and have SR, npoints as its final two arguments.

+

Rounding errors are a real concern/pain in the business of +making waveforms of short duration (few samples). Therefore, +the PulseAtoms take the number of points rather than the +duration as input argument, so that all ambiguity can be handled +in one place (the _subelementBuilder)

+
+
+static arb_func(func: Callable, kwargs, SR, npts)[source]
+

This function is used to generate an arbitrary waveform from a function. +The function must be of the form f(time, **kwargs) where time is a numpy array and +kwargs is a dict that provides any additional parameters needed for the function.

+

Example:

+
func = lambda time, freq, ampl, off, phase: ampl*np.sin(freq*time+phase)+off
+kwargs = {'freq': 1e6, 'ampl': 1, 'off': 0, 'phase': 0}
+
+
+
+ +
+
+static gaussian(ampl, sigma, mu, offset, SR, npts)[source]
+

Returns a Gaussian of peak height ampl (when offset==0)

+

Is by default centred in the middle of the interval

+
+ +
+
+static gaussian_smooth_cutoff(ampl, sigma, mu, offset, SR, npts)[source]
+

Returns a Gaussian of peak height ampl (when offset==0)

+

Is by default centred in the middle of the interval

+

smooth cutoff by making offsetting the Gaussian so endpoint = 0 and normalizing the hight to 1

+
+ +
+
+static ramp(start, stop, SR, npts)[source]
+
+ +
+
+static sine(freq, ampl, off, phase, SR, npts)[source]
+
+ +
+
+static waituntil(dummy, SR, npts)[source]
+
+ +
+ +
+
+broadbean.broadbean.marked_for_deletion(replaced_by: str | None = None) Callable[source]
+

A decorator for functions we want to kill. The function still +gets called.

+
+ +
+
+

broadbean.element module

+
+
+class broadbean.element.Element[source]
+

Bases: object

+

Object representing an element. An element is a collection of waves that +are to be run simultaneously. The element consists of a number of channels +that are then each filled with anything of the appropriate length.

+
+
+property SR
+

Returns the sample rate, if well-defined. Else raises +an error about what went wrong.

+
+ +
+
+addArray(channel: int | str, waveform: ndarray, SR: int, **kwargs) None[source]
+

Add an array of voltage value to the element on the specified channel. +Overwrites whatever was there before. Markers can be specified via +the kwargs, i.e. the kwargs must specify arrays of markers. The names +can be ‘m1’, ‘m2’, ‘m3’, etc.

+
+
Parameters:
+
    +
  • channel – The channel number

  • +
  • waveform – The array of waveform values (V)

  • +
  • SR – The sample rate in Sa/s

  • +
+
+
+
+ +
+
+addBluePrint(channel: str | int, blueprint: BluePrint) None[source]
+

Add a blueprint to the element on the specified channel. +Overwrites whatever was there before.

+
+ +
+
+addFlags(channel: str | int, flags: Sequence[str | int]) None[source]
+

Adds flags for the specified channel. +List of 4 flags, each of which should be 0 or “” for ‘No change’, 1 or “H” for ‘High’, +2 or “L” for ‘Low’, 3 or “T” for ‘Toggle’, 4 or “P” for ‘Pulse’.

+
+ +
+
+changeArg(channel: str | int, name: str, arg: str | int, value: int | float, replaceeverywhere: bool = False) None[source]
+

Change the argument of a function of the blueprint on the specified +channel.

+
+
Parameters:
+
    +
  • channel – The channel where the blueprint sits.

  • +
  • name – The name of the segment in which to change an argument

  • +
  • arg – Either the position (int) or name (str) of +the argument to change

  • +
  • value – The new value of the argument

  • +
  • replaceeverywhere – If True, the same argument is overwritten +in ALL segments where the name matches. E.g. ‘gaussian1’ will +match ‘gaussian’, ‘gaussian2’, etc. If False, only the segment +with exact name match gets a replacement.

  • +
+
+
Raises:
+
    +
  • ValueError – If the specified channel has no blueprint.

  • +
  • ValueError – If the argument can not be matched (either the argument + name does not match or the argument number is wrong).

  • +
+
+
+
+ +
+
+changeDuration(channel: str | int, name: str, newdur: int | float, replaceeverywhere: bool = False) None[source]
+

Change the duration of a segment of the blueprint on the specified +channel

+
+
Parameters:
+
    +
  • channel – The channel holding the blueprint in question

  • +
  • name) – The name of the segment to modify

  • +
  • newdur – The new duration.

  • +
  • replaceeverywhere – If True, all segments +matching the base +name given will have their duration changed. If False, only the +segment with an exact name match will have its duration +changed. Default: False.

  • +
+
+
+
+ +
+
+property channels
+

The channels that has something on them

+
+ +
+
+copy()[source]
+

Return a copy of the element

+
+ +
+
+property description
+

Returns a dict describing the element.

+
+ +
+
+property duration
+

Returns the duration in seconds of the element, if said duration is +well-defined. Else raises an error.

+
+ +
+
+classmethod element_from_description(element_dict)[source]
+

Returns a blueprint from a description given as a dict

+
+
Parameters:
+
    +
  • element_dict – a dict in the same form as returned by

  • +
  • Element.description

  • +
+
+
+
+ +
+
+getArrays(includetime: bool = False) dict[int, dict[str, ndarray]][source]
+

Return arrays of the element. Heavily used by the Sequence.

+
+
Parameters:
+

includetime – Whether to include time arrays. They will have the key +‘time’. Time should be included when plotting, otherwise not.

+
+
Returns:
+

Dictionary with channel numbers (ints) as keys and forged +blueprints as values. A forged blueprint is a dict with +the mandatory key ‘wfm’ and optional keys ‘m1’, ‘m2’, ‘m3’ (etc) +and ‘time’.

+
+
Return type:
+

dict

+
+
+
+ +
+
+classmethod init_from_json(path_to_file: str) Element[source]
+

Reads Element from JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to be read ex:

  • +
  • path_to_file/Element.json

  • +
  • write_to_json (by the function)

  • +
  • writen (The JSON file needs to be structured as if it was)

  • +
  • write_to_json

  • +
+
+
+
+ +
+
+property points: int
+

Returns the number of points of each channel if that number is +well-defined. Else an error is raised.

+
+ +
+
+validateDurations()[source]
+

Check that all channels have the same specified duration, number of +points and sample rate.

+
+ +
+
+write_to_json(path_to_file: str) None[source]
+

Writes element to JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to write to ex:

  • +
  • path_to_file/element.json

  • +
+
+
+
+ +
+ +
+
+exception broadbean.element.ElementDurationError[source]
+

Bases: Exception

+
+ +
+
+

broadbean.plotting module

+
+
+broadbean.plotting.getSIScalingAndPrefix(minmax: tuple[float, float]) tuple[float, str][source]
+

Return the scaling exponent and unit prefix. E.g. (-2e-3, 1e-6) will +return (1e3, ‘m’)

+
+
Parameters:
+

minmax – The (min, max) value of the signal

+
+
Returns:
+

+
A tuple of the scaling (inverse of the prefix) and the prefix

string.

+
+
+

+
+
+
+ +
+
+broadbean.plotting.plotter(obj_to_plot: Sequence | BluePrint | Element, **forger_kwargs) None[source]
+

The one plot function to be called. Turns whatever it gets +into a sequence, forges it, and plots that.

+
+ +
+
+

broadbean.ripasso module

+
+
+exception broadbean.ripasso.MissingFrequenciesError[source]
+

Bases: Exception

+
+ +
+
+broadbean.ripasso.applyCustomTransferFunction(signal, SR, tf_freqs, tf_amp, invert=False)[source]
+

Apply custom transfer function

+

Given a signal, its sample rate, and a provided transfer function, apply +the transfer function to the signal.

+
+
Parameters:
+
    +
  • signal (np.array) – A numpy array containing the signal

  • +
  • SR (int) – The sample rate of the signal (Sa/s)

  • +
  • tf_freqs (np.array) – The frequencies of the transfer function. Must +be monotonically increasing.

  • +
  • tf_amp (np.array) – The amplitude of the transfer function. Must be +dimensionless.

  • +
  • invert (Optional[bool]) – If True, the inverse transfer function is +applied. Default: False.

  • +
+
+
Returns:
+

The modified signal.

+
+
Return type:
+

np.array

+
+
+
+ +
+
+broadbean.ripasso.applyInverseRCFilter(signal, SR, kind, f_cut, order, DCgain=1)[source]
+

Apply the inverse of an RC-circuit filter to a signal and return the +compensated signal.

+

Note that a high-pass filter in principle has identically zero DC +gain which requires an infinite offset to compensate.

+
+
Parameters:
+
    +
  • signal (np.array) – The input signal. The signal is assumed to start at +t=0 and be evenly sampled at sample rate SR.

  • +
  • SR (int) – Sample rate (Sa/s) of the input signal

  • +
  • kind (str) – The type of filter. Either ‘HP’ or ‘LP’.

  • +
  • f_cut (float) – The cutoff frequency of the filter (Hz)

  • +
  • order (int) – The order of the filter. The first order filter is +applied order times.

  • +
  • DCgain (Optional[float]) – The DC gain of the filter. ONLY used by the +high-pass filter. Default: 1.

  • +
+
+
Returns:
+

The filtered signal along the original time axis. Imaginary +parts are discarded prior to return.

+
+
Return type:
+

np.array

+
+
Raises:
+
    +
  • ValueError – If kind is neither ‘HP’ nor ‘LP’

  • +
  • ValueError – If DCgain is zero.

  • +
+
+
+
+ +
+
+broadbean.ripasso.applyRCFilter(signal, SR, kind, f_cut, order, DCgain=0)[source]
+

Apply a simple RC-circuit filter +to signal and return the filtered signal.

+
+
Parameters:
+
    +
  • signal (np.array) – The input signal. The signal is assumed to start at +t=0 and be evenly sampled at sample rate SR.

  • +
  • SR (int) – Sample rate (Sa/s) of the input signal

  • +
  • kind (str) – The type of filter. Either ‘HP’ or ‘LP’.

  • +
  • f_cut (float) – The cutoff frequency of the filter (Hz)

  • +
  • order (int) – The order of the filter. The first order filter is +applied order times.

  • +
  • DCgain (Optional[float]) – The DC gain of the filter. ONLY used by the +high-pass filter. Default: 0.

  • +
+
+
Returns:
+

The filtered signal along the original time axis. Imaginary +parts are discarded prior to return.

+
+
Return type:
+

np.array

+
+
Raises:
+

ValueError – If kind is neither ‘HP’ nor ‘LP’

+
+
+
+ +
+
+

broadbean.sequence module

+
+
+exception broadbean.sequence.InvalidForgedSequenceError[source]
+

Bases: Exception

+
+ +
+
+class broadbean.sequence.Sequence[source]
+

Bases: object

+

Sequence object

+
+
+property SR
+

Returns the sample rate, if defined. Else returns -1.

+
+ +
+
+addElement(position: int, element: Element) None[source]
+

Add an element to the sequence. Overwrites previous values.

+
+
Parameters:
+
    +
  • position (int) – The sequence position of the element (lowest: 1)

  • +
  • element (Element) – An element instance

  • +
+
+
Raises:
+

ValueError – If the element has inconsistent durations

+
+
+
+ +
+
+addSubSequence(position: int, subsequence: Sequence) None[source]
+

Add a subsequence to the sequence. Overwrites anything previously +assigned to this position. The subsequence can not contain any +subsequences itself.

+
+
Parameters:
+
    +
  • position – The sequence position (starting from 1)

  • +
  • subsequence – The subsequence to add

  • +
+
+
+
+ +
+
+property channels
+

Returns a list of the specified channels of the sequence

+
+ +
+
+checkConsistency(verbose=False)[source]
+

Checks wether the sequence can be built, i.e. wether all elements +have waveforms on the same channels and of the same length.

+
+ +
+
+copy()[source]
+

Returns a copy of the sequence.

+
+ +
+
+property description
+

Return a dictionary fully describing the Sequence.

+
+ +
+
+property duration: float
+

Returns the duration in seconds of the sequence.

+
+ +
+
+element(pos)[source]
+

Returns the element at the given position. Changes made to the return +value of this methods will apply to the sequence. If this is undesired, +make a copy of the returned element using Element.copy

+
+
Parameters:
+

pos (int) – The sequence position

+
+
Raises:
+

KeyError – If no element is specified at the given position

+
+
+
+ +
+
+forge(apply_delays: bool = True, apply_filters: bool = True, includetime: bool = False) dict[int, dict][source]
+

Forge the sequence, applying all specified transformations +(delays and ripasso filter corrections). Copies the data, so +that the sequence is not modified by forging.

+
+
Parameters:
+
    +
  • apply_delays – Whether to apply the assigned channel delays +(if any)

  • +
  • apply_filters – Whether to apply the assigned channel filters +(if any)

  • +
  • includetime – Whether to include the time axis and the segment +durations (a list) with the arrays. Used for plotting.

  • +
+
+
Returns:
+

A nested dictionary holding the forged sequence.

+
+
+
+ +
+
+classmethod init_from_json(path_to_file: str) Sequence[source]
+

Reads sequense from JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to be read ex:

  • +
  • path_to_file/sequense.json

  • +
  • write_to_json (by the function)

  • +
  • writen (The JSON file needs to be structured as if it was)

  • +
  • write_to_json

  • +
+
+
+
+ +
+
+property length_sequenceelements
+

Returns the current number of specified sequence elements

+
+ +
+
+property name
+
+ +
+
+outputForAWGFile()[source]
+

Returns a sliceable object with items matching the call +signature of the ‘make_*_awg_file’ functions of the QCoDeS +AWG5014 driver. One may then construct an awg file as follows +(assuming that seq is the sequence object):

+
package = seq.outputForAWGFile()
+make_awg_file(*package[:], **kwargs)
+
+
+
+ +
+
+outputForSEQXFile() tuple[list[int], list[int], list[int], list[int], list[int], list[list[ndarray]], list[float], str][source]
+

Generate a tuple matching the call signature of the QCoDeS +AWG70000A driver’s makeSEQXFile function. If channel delays +have been specified, they are added to the ouput before exporting. +The intended use of this function together with the QCoDeS driver is

+
pkg = seq.outputForSEQXFile()
+seqx = awg70000A.makeSEQXFile(*pkg)
+
+
+
+
Returns:
+

+
A tuple holding (trig_waits, nreps, event_jumps, event_jump_to,

go_to, wfms, amplitudes, seqname)

+
+
+

+
+
+
+ +
+
+outputForSEQXFileWithFlags() tuple[list[int], list[int], list[int], list[int], list[int], list[list[ndarray]], list[float], str, list[list[list[int]]]][source]
+

Generate a tuple matching the call signature of the QCoDeS +AWG70000A driver’s makeSEQXFile function. Same as outputForSEQXFile(), +but also includes information about the flags.

+
+
Returns:
+

+
A tuple holding (trig_waits, nreps, event_jumps, event_jump_to,

go_to, wfms, amplitudes, seqname, flags)

+
+
+

+
+
+
+ +
+
+property points
+

Returns the number of points of the sequence, disregarding +sequencing info (like repetitions). Useful for asserting upload +times, i.e. the size of the built sequence.

+
+ +
+
+classmethod sequence_from_description(seq_dict: dict) Sequence[source]
+

Returns a sequence from a description given as a dict

+
+
Parameters:
+
    +
  • seq_dict – a dict in the same form as returned by

  • +
  • Sequence.description

  • +
+
+
+
+ +
+
+setChannelAmplitude(channel: int | str, ampl: float) None[source]
+

Assign the physical voltage amplitude of the channel. This is used +when making output for real instruments.

+
+
Parameters:
+
    +
  • channel – The channel number

  • +
  • ampl – The channel peak-to-peak amplitude (V)

  • +
+
+
+
+ +
+
+setChannelDelay(channel: int | str, delay: float) None[source]
+

Assign a delay to a channel. This is used when making output for .awg +files. Use the delay to compensate for cable length differences etc. +Zeros are prepended to the waveforms to delay them and correspondingly +appended to non (or less) delayed channels.

+
+
Parameters:
+
    +
  • channel – The channel number/name

  • +
  • delay – The required delay (s)

  • +
+
+
Raises:
+

ValueError – If a non-integer or non-non-negative channel number is + given.

+
+
+
+ +
+
+setChannelFilterCompensation(channel: str | int, kind: str, order: int = 1, f_cut: float | None = None, tau: float | None = None) None[source]
+

Specify a filter to compensate for.

+

The specified channel will get a compensation (pre-distorion) to +compensate for the specified frequency filter. Just to be clear: +the INVERSE transfer function of the one you specify is applied. +Only compensation for simple RC-circuit type high pass and low +pass is supported.

+
+
Parameters:
+
    +
  • channel – The channel to apply this to.

  • +
  • kind – Either ‘LP’ or ‘HP’

  • +
  • order – The order of the filter to compensate for. +May be negative. Default: 1.

  • +
  • f_cut – The cut_off frequency (Hz).

  • +
  • tau) – The time constant (s). Note that +tau = 1/f_cut and that only one of the two can be specified.

  • +
+
+
Raises:
+
    +
  • ValueError – If kind is not ‘LP’ or ‘HP’

  • +
  • ValueError – If order is not an int.

  • +
  • SpecificationInconsistencyError – If both f_cut and tau are given.

  • +
+
+
+
+ +
+
+setChannelOffset(channel: int | str, offset: float) None[source]
+

Assign the physical voltage offset of the channel. This is used +by some backends when making output for real instruments

+
+
Parameters:
+
    +
  • channel – The channel number/name

  • +
  • offset – The channel offset (V)

  • +
+
+
+
+ +
+
+setChannelVoltageRange(channel, ampl, offset)[source]
+

Assign the physical voltages of the channel. This is used when making +output for .awg files. The corresponding parameters in the QCoDeS +AWG5014 driver are called chXX_amp and chXX_offset. Please ensure that +the channel in question is indeed in ampl/offset mode and not in +high/low mode.

+
+
Parameters:
+
    +
  • channel (int) – The channel number

  • +
  • ampl (float) – The channel peak-to-peak amplitude (V)

  • +
  • offset (float) – The channel offset (V)

  • +
+
+
+
+ +
+
+setSR(SR)[source]
+

Set the sample rate for the sequence

+
+ +
+
+setSequenceSettings(pos, wait, nreps, jump, goto)[source]
+

Set the sequence setting for the sequence element at pos.

+
+
Parameters:
+
    +
  • pos (int) – The sequence element (counting from 1)

  • +
  • wait (int) – The wait state specifying whether to wait for a +trigger. 0: OFF, don’t wait, 1: ON, wait. For some backends, +additional integers are allowed to specify the trigger input. +0 always means off.

  • +
  • nreps (int) – Number of repetitions. 0 corresponds to infinite +repetitions

  • +
  • jump (int) – Event jump target, the position of a sequence element. +If 0, the event jump state is off.

  • +
  • goto (int) – Goto target, the position of a sequence element. +0 means next.

  • +
+
+
+
+ +
+
+setSequencingEventInput(pos: int, jump_input: int) None[source]
+

Set the event input for the sequence element at pos. This setting is +ignored by the AWG 5014.

+
+
Parameters:
+
    +
  • pos – The sequence element (counting from 1)

  • +
  • jump_input – The input specifier, 0 for off, +1 for ‘TrigA’, 2 for ‘TrigB’, 3 for ‘Internal’.

  • +
+
+
+
+ +
+
+setSequencingEventJumpTarget(pos: int, jump_target: int) None[source]
+

Set the event jump target for the sequence element at pos.

+
+
Parameters:
+
    +
  • pos – The sequence element (counting from 1)

  • +
  • jump_target – The sequence element to jump to (counting from 1)

  • +
+
+
+
+ +
+
+setSequencingGoto(pos: int, goto: int) None[source]
+

Set the goto target (which element to play after the current one ends) +for the sequence element at pos.

+
+
Parameters:
+
    +
  • pos – The sequence element (counting from 1)

  • +
  • goto – The position of the element to play. 0 means ‘next in line’

  • +
+
+
+
+ +
+
+setSequencingNumberOfRepetitions(pos: int, nrep: int) None[source]
+

Set the number of repetitions for the sequence element at pos.

+
+
Parameters:
+
    +
  • pos – The sequence element (counting from 1)

  • +
  • nrep – The number of repetitions (0 means infinite)

  • +
+
+
+
+ +
+
+setSequencingTriggerWait(pos: int, wait: int) None[source]
+

Set the trigger wait for the sequence element at pos. For +AWG 5014 out, this can be 0 or 1, For AWG 70000A output, this +can be 0, 1, 2, or 3.

+
+
Parameters:
+
    +
  • pos – The sequence element (counting from 1)

  • +
  • wait – The wait state/input depending on backend.

  • +
+
+
+
+ +
+
+write_to_json(path_to_file: str) None[source]
+

Writes sequences to JSON file

+
+
Parameters:
+
    +
  • path_to_file – the path to the file to write to ex:

  • +
  • path_to_file/sequense.json

  • +
+
+
+
+ +
+ +
+
+exception broadbean.sequence.SequenceCompatibilityError[source]
+

Bases: Exception

+
+ +
+
+exception broadbean.sequence.SequenceConsistencyError[source]
+

Bases: Exception

+
+ +
+
+exception broadbean.sequence.SequencingError[source]
+

Bases: Exception

+
+ +
+
+exception broadbean.sequence.SpecificationInconsistencyError[source]
+

Bases: Exception

+
+ +
+
+

broadbean.tools module

+
+
+broadbean.tools.makeLinearlyVaryingSequence(baseelement, channel, name, arg, start, stop, step)[source]
+

Make a pulse sequence where a single parameter varies linearly. +The pulse sequence will consist of N copies of the same element with just +the specified argument changed (N = abs(stop-start)/steps)

+
+
Parameters:
+
    +
  • baseelement (Element) – The basic element.

  • +
  • channel (int) – The channel where the change should happen

  • +
  • name (str) – Name of the blueprint segment to change

  • +
  • arg (Union[str, int]) – Name (str) or position (int) of the argument +to change. If the arg is ‘duration’, the duration is changed +instead.

  • +
  • start (float) – Start point of the variation (included)

  • +
  • stop (float) – Stop point of the variation (included)

  • +
  • step (float) – Increment of the variation

  • +
+
+
+
+ +
+
+broadbean.tools.makeVaryingSequence(baseelement, channels, names, args, iters)[source]
+

Make a pulse sequence where N parameters vary simultaneously in M steps. +The user inputs a baseelement which is copied M times and changed +according to the given inputs.

+
+
Parameters:
+
    +
  • baseelement (Element) – The basic element.

  • +
  • channels (Union[list, tuple]) – Either a list or a tuple of channels on +which to find the blueprint to change. Must have length N.

  • +
  • names (Union[list, tuple]) – Either a list or a tuple of names of the +segment to change. Must have length N.

  • +
  • args (Union[list, tuple]) – Either a list or a tuple of argument +specifications for the argument to change. Use ‘duration’ to change +the segment duration. Must have length N.

  • +
  • iters (Union[list, tuple]) – Either a list or a tuple of length N +containing Union[list, tuple, range] of length M.

  • +
+
+
Raises:
+
    +
  • ValueError – If not channels, names, args, and iters are of the same + length.

  • +
  • ValueError – If not each iter in iters specifies the same number of + values.

  • +
+
+
+
+ +
+
+broadbean.tools.repeatAndVarySequence(seq, poss, channels, names, args, iters)[source]
+

Repeat a sequence and vary part(s) of it. Returns a new sequence. +Given N specifications of M steps, N parameters are varied in M +steps.

+
+
Parameters:
+
    +
  • seq (Sequence) – The sequence to be repeated.

  • +
  • poss (Union[list, tuple]) – A length N list/tuple specifying at which +sequence position(s) the blueprint to change is.

  • +
  • channels (Union[list, tuple]) – A length N list/tuple specifying on +which channel(s) the blueprint to change is.

  • +
  • names (Union[list, tuple]) – A length N list/tuple specifying the name +of the segment to change.

  • +
  • args (Union[list, tuple]) – A length N list/tuple specifying which +argument to change. A valid argument is also ‘duration’.

  • +
  • iters (Union[list, tuple]) – A length N list/tuple containing length +M indexable iterables with the values to step through.

  • +
+
+
+
+ +
+
+

Module contents

+
+
+ +
+
+ +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/changes/0.10.0.html b/changes/0.10.0.html new file mode 100644 index 000000000..6c9608e60 --- /dev/null +++ b/changes/0.10.0.html @@ -0,0 +1,349 @@ + + + + + + + + + Changelog for broadbean 0.10.0 - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Changelog for broadbean 0.10.0

+

The January 2021 release of broadbean.

+
+

Breaking Changes:

+

There are no breaking changes in this release of broadbean

+
+
+

New:

+
    +
  • Support for reading and writing broadbean Sequences, elements and bluprints to and from a json file

  • +
  • made broadbean compatiple with numpy version 1.18

  • +
  • Include LICENSE in package

  • +
+
+
+ +
+
+ +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/changes/0.11.0.html b/changes/0.11.0.html new file mode 100644 index 000000000..997093f21 --- /dev/null +++ b/changes/0.11.0.html @@ -0,0 +1,383 @@ + + + + + + + + + Changelog for broadbean 0.11.0 - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Changelog for broadbean 0.11.0

+

The August 2022 release of broadbean with many great improvements for modernizing broadbean’s infrastructure.

+
+

Breaking Changes:

+
    +
  • Methods and functions marked for deletion in version 0.10.0 have now been removed. Specifically +BluePrint.plot, broadbean.bluePrintPlotter, Element.plotElement, Sequence.plotSequence +and Sequence.plotAWGOutput. (#107)

  • +
+
+
+

New:

+
    +
  • New Sequence method outputForSEQXFileWithFlags for setting flags for every element forming a sequence. +This is useful for auxiliary outputs on a Tektronix AWG70000. (#101)

  • +
+
+
+

Improved:

+
    +
  • Fix for invalid identity comparisons in blueprint submodule. (#102)

  • +
+
+
+

Behind The Scenes:

+
    +
  • Updated README.md that includes updated link to broadbean documentation. (#158)

  • +
  • Replace Conda test job with regular pip job on windows. (#139)

  • +
  • Enable dependabot for broadbean. (#111)

  • +
  • Enable precommit hook. (#110)

  • +
  • Documentation infrastructure improvements. (#110)

  • +
  • Modernize setup and build infrastructure (convert to pep516/517, build wheels and sdist using build, +automatic upload to pypi, move config to pyproject.toml and setup.cfg and pinning dependencies with +requirements.txt). (#109)

  • +
  • Move tests into package to include them into distribution. (#108)

  • +
  • Use GitHub actions, test on python 3.7-3.10, remove python 3.6 support, remove Travis and AppVeyor. (#103)

  • +
+
+
+ +
+
+ +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/changes/index.html b/changes/index.html new file mode 100644 index 000000000..5e4ba92e3 --- /dev/null +++ b/changes/index.html @@ -0,0 +1,340 @@ + + + + + + + + + Changelogs - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + + + + + \ No newline at end of file diff --git a/examples/Example_Write_Read_JSON.html b/examples/Example_Write_Read_JSON.html new file mode 100644 index 000000000..44ff4731c --- /dev/null +++ b/examples/Example_Write_Read_JSON.html @@ -0,0 +1,7727 @@ + + + + + + + + + Read and Write from JSON file Tutorial - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Read and Write from JSON file Tutorial

+
+
[1]:
+
+
+
#
+# IMPORTS
+#
+%matplotlib nbagg
+import matplotlib as mpl
+
+import broadbean as bb
+from broadbean.plotting import plotter
+
+mpl.rcParams["figure.figsize"] = (8, 3)
+mpl.rcParams["figure.subplot.bottom"] = 0.15
+
+
+
+
+

Read and Write BluePrint from JSON file Tutorial

+
+

Building a BluePrint

+
+
[2]:
+
+
+
# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.
+ramp = bb.PulseAtoms.ramp  # args: start, stop
+
+# Create the blueprints
+bp_square = bb.BluePrint()
+bp_square.setSR(1e9)
+bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)
+bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name="top", dur=100e-9)
+bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)
+bp_boxes = bp_square + bp_square
+plotter(bp_boxes)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Writing the BluePrint to a file

+
+
[3]:
+
+
+
bp_boxes.write_to_json("blue.json")
+
+
+
+

Reading the BluePrint back from the file

+
+
[4]:
+
+
+
bp_boxes_read = bb.BluePrint.init_from_json("blue.json")
+bp_boxes_read.setSR(
+    1e9
+)  # note the blueprint readback do not have a sample rate attached
+plotter(bp_boxes_read)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Test if the BluePrints are identical

+
+
[5]:
+
+
+
print(bp_boxes == bp_boxes_read)
+
+
+
+
+
+
+
+
+True
+
+
+
+
+
+

Read and Write Element from JSON file Tutorial

+

Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array

+
+
[6]:
+
+
+
######################################################
+# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.
+ramp = bb.PulseAtoms.ramp  # args: start, stop
+sine = bb.PulseAtoms.sine  # args: freq, ampl, off, phase
+seq1 = bb.Sequence()
+
+# We fill up the sequence by adding elements at different sequence positions.
+# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3
+
+#
+# Make blueprints, make elements
+
+# Create the blueprints
+bp_square = bb.BluePrint()
+bp_square.setSR(1e9)
+bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)
+bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name="top", dur=100e-9)
+bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)
+bp_boxes = bp_square + bp_square
+#
+bp_sine = bb.BluePrint()
+bp_sine.setSR(1e9)
+bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)
+bp_sineandboxes = bp_sine + bp_square
+
+bp_sineandboxes.setSegmentMarker(
+    "ramp", (-0.0, 100e-9), 1
+)  # segment name, (delay, duration), markerID
+bp_sineandboxes.setSegmentMarker(
+    "sine", (-0.0, 100e-9), 2
+)  # segment name, (delay, duration), markerID
+# make marker 2 go ON halfway through the sine
+# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)
+
+
+# create elements
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp_boxes)
+elem1.addBluePrint(3, bp_sineandboxes)
+plotter(elem1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Writing the Element to a file

+
+
[7]:
+
+
+
elem1.write_to_json("elem.json")
+plotter(elem1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Reading the Element back from the file

+
+
[8]:
+
+
+
elem_read = bb.Element.init_from_json("elem.json")
+
+
+
+

Test if the Element.description are identical

+
+
[9]:
+
+
+
print(
+    elem1.description == elem_read.description
+)  ## note that the elements are not identical only the discription
+
+
+
+
+
+
+
+
+True
+
+
+
+
+

Read and Write Sequence from JSON file Tutorial

+

Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array

+
+
[10]:
+
+
+
######################################################
+# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.
+ramp = bb.PulseAtoms.ramp  # args: start, stop
+sine = bb.PulseAtoms.sine  # args: freq, ampl, off, phase
+seq1 = bb.Sequence()
+
+# We fill up the sequence by adding elements at different sequence positions.
+# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3
+
+#
+# Make blueprints, make elements
+
+# Create the blueprints
+bp_square = bb.BluePrint()
+bp_square.setSR(1e9)
+bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)
+bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name="top", dur=100e-9)
+bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)
+bp_boxes = bp_square + bp_square
+#
+bp_sine = bb.BluePrint()
+bp_sine.setSR(1e9)
+bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)
+bp_sineandboxes = bp_sine + bp_square
+
+bp_sineandboxes.setSegmentMarker(
+    "ramp", (-0.0, 100e-9), 1
+)  # segment name, (delay, duration), markerID
+bp_sineandboxes.setSegmentMarker(
+    "sine", (-0.0, 100e-9), 2
+)  # segment name, (delay, duration), markerID
+# make marker 2 go ON halfway through the sine
+# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)
+
+
+# create elements
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp_boxes)
+elem1.addBluePrint(3, bp_sineandboxes)
+#
+elem2 = bb.Element()
+elem2.addBluePrint(3, bp_boxes)
+elem2.addBluePrint(1, bp_sineandboxes)
+
+# Fill up the sequence
+seq1.addElement(1, elem1)  # Call signature: seq. pos., element
+seq1.addElement(2, elem2)
+seq1.addElement(3, elem1)
+
+# set its sample rate
+seq1.setSR(elem1.SR)
+
+seq1.setChannelAmplitude(1, 10e-3)  # Call signature: channel, amplitude (peak-to-peak)
+seq1.setChannelOffset(1, 0)
+seq1.setChannelAmplitude(3, 10e-3)
+seq1.setChannelOffset(3, 0)
+
+# Here we repeat each element twice and then proceed to the next, wrapping over at the end
+seq1.setSequencingTriggerWait(1, 0)
+seq1.setSequencingNumberOfRepetitions(1, 2)
+seq1.setSequencingEventJumpTarget(1, 0)
+seq1.setSequencingGoto(1, 2)
+#
+seq1.setSequencingTriggerWait(2, 0)
+seq1.setSequencingNumberOfRepetitions(2, 2)
+seq1.setSequencingEventJumpTarget(2, 0)
+seq1.setSequencingGoto(2, 3)
+#
+seq1.setSequencingTriggerWait(3, 0)
+seq1.setSequencingNumberOfRepetitions(3, 2)
+seq1.setSequencingEventJumpTarget(3, 0)
+seq1.setSequencingGoto(3, 1)
+plotter(seq1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Writing the Sequence to a file

+
+
[11]:
+
+
+
seq1.write_to_json("testdata.json")
+
+
+
+
+
[12]:
+
+
+
bp_boxes.write_to_json("blue.json")
+
+
+
+
+
+

Reading the Sequence back from the file

+
+
[13]:
+
+
+
seq = bb.Sequence.init_from_json("testdata.json")
+
+
+
+
+
[14]:
+
+
+
plotter(seq)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

Test if the Sequences are identical

+
+
[15]:
+
+
+
print(seq == seq1)
+
+
+
+
+
+
+
+
+True
+
+
+
+
+
+
+

Ekstras

+

Example of how read and write blueprint from/to Sequence json file if needed.

+
+
[16]:
+
+
+
def readblueprint(path: str, element: int = 1, channel: int = 1):
+    tempseq = bb.Sequence.init_from_json(path)
+    return tempseq.element(element)._data[channel]["blueprint"]
+
+
+
+
+
[17]:
+
+
+
def writeblueprint(
+    blueprint, path: str, SR: float = 1e9, SeqAmp: float = 10e-3, SeqOffset: float = 0
+):  # -> None
+    if blueprint.SR is None:
+        blueprint.setSR(SR)
+    elemtmp = bb.Element()
+    seqtmp = bb.Sequence()
+    elemtmp.addBluePrint(1, blueprint)
+    seqtmp.addElement(1, elemtmp)
+    seqtmp.setSR(blueprint.SR)
+    seqtmp.setChannelAmplitude(1, SeqAmp)
+    seqtmp.setChannelOffset(1, 0)
+    seqtmp.write_to_json(path)
+
+
+
+
+
[18]:
+
+
+
writeblueprint(bp_boxes, "boxes.json")
+
+
+
+
+
[19]:
+
+
+
bp_one_two = readblueprint("testdata.json", element=2, channel=1)
+plotter(bp_one_two)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[ ]:
+
+
+

+
+
+
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/Example_Write_Read_JSON.ipynb b/examples/Example_Write_Read_JSON.ipynb new file mode 100644 index 000000000..0567df68e --- /dev/null +++ b/examples/Example_Write_Read_JSON.ipynb @@ -0,0 +1,7677 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Read and Write from JSON file Tutorial" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:10.633038Z", + "iopub.status.busy": "2025-01-21T06:30:10.632869Z", + "iopub.status.idle": "2025-01-21T06:30:11.003348Z", + "shell.execute_reply": "2025-01-21T06:30:11.002800Z" + } + }, + "outputs": [], + "source": [ + "#\n", + "# IMPORTS\n", + "#\n", + "%matplotlib nbagg\n", + "import matplotlib as mpl\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "mpl.rcParams[\"figure.figsize\"] = (8, 3)\n", + "mpl.rcParams[\"figure.subplot.bottom\"] = 0.15" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write BluePrint from JSON file Tutorial" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Building a BluePrint" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.005746Z", + "iopub.status.busy": "2025-01-21T06:30:11.005302Z", + "iopub.status.idle": "2025-01-21T06:30:11.029028Z", + "shell.execute_reply": "2025-01-21T06:30:11.028487Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "plotter(bp_boxes)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Writing the BluePrint to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.048618Z", + "iopub.status.busy": "2025-01-21T06:30:11.048444Z", + "iopub.status.idle": "2025-01-21T06:30:11.051933Z", + "shell.execute_reply": "2025-01-21T06:30:11.051359Z" + } + }, + "outputs": [], + "source": [ + "bp_boxes.write_to_json(\"blue.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Reading the BluePrint back from the file" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.053753Z", + "iopub.status.busy": "2025-01-21T06:30:11.053435Z", + "iopub.status.idle": "2025-01-21T06:30:11.074531Z", + "shell.execute_reply": "2025-01-21T06:30:11.073954Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bp_boxes_read = bb.BluePrint.init_from_json(\"blue.json\")\n", + "bp_boxes_read.setSR(\n", + " 1e9\n", + ") # note the blueprint readback do not have a sample rate attached\n", + "plotter(bp_boxes_read)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test if the BluePrints are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.076484Z", + "iopub.status.busy": "2025-01-21T06:30:11.076109Z", + "iopub.status.idle": "2025-01-21T06:30:11.079262Z", + "shell.execute_reply": "2025-01-21T06:30:11.078746Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(bp_boxes == bp_boxes_read)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write Element from JSON file Tutorial\n", + "Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.080846Z", + "iopub.status.busy": "2025-01-21T06:30:11.080676Z", + "iopub.status.idle": "2025-01-21T06:30:11.127514Z", + "shell.execute_reply": "2025-01-21T06:30:11.126938Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "######################################################\n", + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", + "seq1 = bb.Sequence()\n", + "\n", + "# We fill up the sequence by adding elements at different sequence positions.\n", + "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", + "\n", + "#\n", + "# Make blueprints, make elements\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"ramp\", (-0.0, 100e-9), 1\n", + ") # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"sine\", (-0.0, 100e-9), 2\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", + "\n", + "\n", + "# create elements\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Writing the Element to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.129534Z", + "iopub.status.busy": "2025-01-21T06:30:11.129195Z", + "iopub.status.idle": "2025-01-21T06:30:11.173626Z", + "shell.execute_reply": "2025-01-21T06:30:11.173013Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "elem1.write_to_json(\"elem.json\")\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Reading the Element back from the file" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.175571Z", + "iopub.status.busy": "2025-01-21T06:30:11.175197Z", + "iopub.status.idle": "2025-01-21T06:30:11.179913Z", + "shell.execute_reply": "2025-01-21T06:30:11.179373Z" + } + }, + "outputs": [], + "source": [ + "elem_read = bb.Element.init_from_json(\"elem.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Test if the Element.description are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.181614Z", + "iopub.status.busy": "2025-01-21T06:30:11.181209Z", + "iopub.status.idle": "2025-01-21T06:30:11.185672Z", + "shell.execute_reply": "2025-01-21T06:30:11.185139Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(\n", + " elem1.description == elem_read.description\n", + ") ## note that the elements are not identical only the discription" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Read and Write Sequence from JSON file Tutorial\n", + "Note only works if the Element is generated from BluePrints not if the Elements contains part generated directly form a numpy array " + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.187552Z", + "iopub.status.busy": "2025-01-21T06:30:11.187055Z", + "iopub.status.idle": "2025-01-21T06:30:11.280531Z", + "shell.execute_reply": "2025-01-21T06:30:11.280022Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "######################################################\n", + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", + "seq1 = bb.Sequence()\n", + "\n", + "# We fill up the sequence by adding elements at different sequence positions.\n", + "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", + "\n", + "#\n", + "# Make blueprints, make elements\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"ramp\", (-0.0, 100e-9), 1\n", + ") # segment name, (delay, duration), markerID\n", + "bp_sineandboxes.setSegmentMarker(\n", + " \"sine\", (-0.0, 100e-9), 2\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "# bp_rtm.setSegmentMarker('mysine', (0.75, 0.1), 2)\n", + "\n", + "\n", + "# create elements\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "#\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(3, bp_boxes)\n", + "elem2.addBluePrint(1, bp_sineandboxes)\n", + "\n", + "# Fill up the sequence\n", + "seq1.addElement(1, elem1) # Call signature: seq. pos., element\n", + "seq1.addElement(2, elem2)\n", + "seq1.addElement(3, elem1)\n", + "\n", + "# set its sample rate\n", + "seq1.setSR(elem1.SR)\n", + "\n", + "seq1.setChannelAmplitude(1, 10e-3) # Call signature: channel, amplitude (peak-to-peak)\n", + "seq1.setChannelOffset(1, 0)\n", + "seq1.setChannelAmplitude(3, 10e-3)\n", + "seq1.setChannelOffset(3, 0)\n", + "\n", + "# Here we repeat each element twice and then proceed to the next, wrapping over at the end\n", + "seq1.setSequencingTriggerWait(1, 0)\n", + "seq1.setSequencingNumberOfRepetitions(1, 2)\n", + "seq1.setSequencingEventJumpTarget(1, 0)\n", + "seq1.setSequencingGoto(1, 2)\n", + "#\n", + "seq1.setSequencingTriggerWait(2, 0)\n", + "seq1.setSequencingNumberOfRepetitions(2, 2)\n", + "seq1.setSequencingEventJumpTarget(2, 0)\n", + "seq1.setSequencingGoto(2, 3)\n", + "#\n", + "seq1.setSequencingTriggerWait(3, 0)\n", + "seq1.setSequencingNumberOfRepetitions(3, 2)\n", + "seq1.setSequencingEventJumpTarget(3, 0)\n", + "seq1.setSequencingGoto(3, 1)\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Writing the Sequence to a file" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.282436Z", + "iopub.status.busy": "2025-01-21T06:30:11.282020Z", + "iopub.status.idle": "2025-01-21T06:30:11.287063Z", + "shell.execute_reply": "2025-01-21T06:30:11.286602Z" + } + }, + "outputs": [], + "source": [ + "seq1.write_to_json(\"testdata.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.288807Z", + "iopub.status.busy": "2025-01-21T06:30:11.288485Z", + "iopub.status.idle": "2025-01-21T06:30:11.292749Z", + "shell.execute_reply": "2025-01-21T06:30:11.292190Z" + } + }, + "outputs": [], + "source": [ + "bp_boxes.write_to_json(\"blue.json\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Reading the Sequence back from the file " + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.294283Z", + "iopub.status.busy": "2025-01-21T06:30:11.294125Z", + "iopub.status.idle": "2025-01-21T06:30:11.300558Z", + "shell.execute_reply": "2025-01-21T06:30:11.300095Z" + } + }, + "outputs": [], + "source": [ + "seq = bb.Sequence.init_from_json(\"testdata.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.302102Z", + "iopub.status.busy": "2025-01-21T06:30:11.301941Z", + "iopub.status.idle": "2025-01-21T06:30:11.428184Z", + "shell.execute_reply": "2025-01-21T06:30:11.427648Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotter(seq)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Test if the Sequences are identical" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.430162Z", + "iopub.status.busy": "2025-01-21T06:30:11.429831Z", + "iopub.status.idle": "2025-01-21T06:30:11.433652Z", + "shell.execute_reply": "2025-01-21T06:30:11.433130Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "True\n" + ] + } + ], + "source": [ + "print(seq == seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Ekstras\n", + "Example of how read and write blueprint from/to Sequence json file if needed." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.435711Z", + "iopub.status.busy": "2025-01-21T06:30:11.435386Z", + "iopub.status.idle": "2025-01-21T06:30:11.439676Z", + "shell.execute_reply": "2025-01-21T06:30:11.439106Z" + } + }, + "outputs": [], + "source": [ + "def readblueprint(path: str, element: int = 1, channel: int = 1):\n", + " tempseq = bb.Sequence.init_from_json(path)\n", + " return tempseq.element(element)._data[channel][\"blueprint\"]" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.441552Z", + "iopub.status.busy": "2025-01-21T06:30:11.441147Z", + "iopub.status.idle": "2025-01-21T06:30:11.446147Z", + "shell.execute_reply": "2025-01-21T06:30:11.445578Z" + } + }, + "outputs": [], + "source": [ + "def writeblueprint(\n", + " blueprint, path: str, SR: float = 1e9, SeqAmp: float = 10e-3, SeqOffset: float = 0\n", + "): # -> None\n", + " if blueprint.SR is None:\n", + " blueprint.setSR(SR)\n", + " elemtmp = bb.Element()\n", + " seqtmp = bb.Sequence()\n", + " elemtmp.addBluePrint(1, blueprint)\n", + " seqtmp.addElement(1, elemtmp)\n", + " seqtmp.setSR(blueprint.SR)\n", + " seqtmp.setChannelAmplitude(1, SeqAmp)\n", + " seqtmp.setChannelOffset(1, 0)\n", + " seqtmp.write_to_json(path)" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.447825Z", + "iopub.status.busy": "2025-01-21T06:30:11.447428Z", + "iopub.status.idle": "2025-01-21T06:30:11.452244Z", + "shell.execute_reply": "2025-01-21T06:30:11.451691Z" + } + }, + "outputs": [], + "source": [ + "writeblueprint(bp_boxes, \"boxes.json\")" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:11.453937Z", + "iopub.status.busy": "2025-01-21T06:30:11.453621Z", + "iopub.status.idle": "2025-01-21T06:30:11.477124Z", + "shell.execute_reply": "2025-01-21T06:30:11.476526Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "bp_one_two = readblueprint(\"testdata.json\", element=2, channel=1)\n", + "plotter(bp_one_two)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/examples/Filter_compensation.html b/examples/Filter_compensation.html new file mode 100644 index 000000000..ee494ac05 --- /dev/null +++ b/examples/Filter_compensation.html @@ -0,0 +1,4591 @@ + + + + + + + + + Filter compensation with the ripasso module - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Filter compensation with the ripasso module

+

The broadbean module gets a helping hand from the ripasso module when it needs to compensate for transfer functions of transmission lines.

+

The ripasso module lets us apply filters and their inverses to signals.

+

The module has two built-in types of filter, namely an RC-circuit high-pass and an RC-circuit low-pass. The transfer functions are

+
+
+\[H_n(f)=\left(\frac{2\pi f\mathrm{i}\tau}{1+2\pi f\mathrm{i}\tau }\right)^{n},\]
+
+

for the nth order high-pass and

+
+
+\[L_n(f)=\left(\frac{1}{1+2\pi f\mathrm{i}\tau }\right)^{n},\]
+
+

for the nth order low-pass. The parameter \(\tau\) is one over the cut-off frequency.

+

The inversion (filter compensation) is performed by mulitplying with the inverse transfer function in frequency space and transforming back to the time domain, e.g. for a given signal \(s(t)\) and a high-pass filter of order n,

+
+
+\[s_\text{filtered}(t) = \mathcal{F}^{-1}[\mathcal{F}[s](f)\cdot H_n(f)](t)\]
+
+

and

+
+
+\[s_\text{compensated}(t) = \mathcal{F}^{-1}[\mathcal{F}[s](f)\cdot H_{-n}(f)](t).\]
+
+
+
[1]:
+
+
+
%matplotlib notebook
+import matplotlib.pyplot as plt
+import numpy as np
+
+from broadbean.ripasso import (
+    applyCustomTransferFunction,
+    applyInverseRCFilter,
+    applyRCFilter,
+)
+
+
+
+
+
[2]:
+
+
+
def squarewave(npts, periods=5):
+    periods = int(periods)
+    array = np.zeros(npts)
+
+    for n in range(periods):
+        array[int(n * npts / periods) : int((2 * n + 1) * npts / 2 / periods)] = 1
+
+    return array
+
+
+
+
+
+

RC Filters

+
+
[3]:
+
+
+
# Let's make some signals!
+
+SR = int(10e3)
+npts = int(10e3)
+
+f_cut = 12  # filter cut-off frequency (Hz)
+
+time = np.linspace(0, npts / SR, npts)
+T = time[-1]
+
+signal1 = np.sin(2 * np.pi * 5 / T * time) + 0.7 * np.cos(2 * np.pi * 1 / T * time)
+signal2 = squarewave(npts, periods=4)
+
+
+
+
+
[4]:
+
+
+
# Then we may high-pass them or low-pass them
+
+filtertype = "HP"
+# filtertype = 'LP'
+
+signal1_filt = applyRCFilter(signal1, SR, filtertype, f_cut, order=1)
+signal2_filt = applyRCFilter(signal2, SR, filtertype, f_cut, order=1)
+
+
+
+
+
[5]:
+
+
+
# And inspect the result
+
+fig, axs = plt.subplots(2, 1)
+axs[0].plot(time, signal1, label="Input signal")
+axs[0].plot(time, signal1_filt, label="Filtered")
+axs[0].set_xlabel("Time (s)")
+axs[0].set_ylabel("Signal (a.u.)")
+axs[0].legend()
+
+axs[1].plot(time, signal2, label="Input signal")
+axs[1].plot(time, signal2_filt, label="Filtered")
+axs[1].set_xlabel("Time (s)")
+axs[1].set_ylabel("Signal (a.u.)")
+axs[1].legend()
+
+plt.tight_layout()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[6]:
+
+
+
# Similarly, we may pre-compensate for the filters
+
+# Note that we specify a DC gain for the inverse filters. This is because it is not possible
+# to compensate for the true DC gain of zero of an RC high pass. Instead, we specify the
+# gain of the high-pass filter at DC. The compensated signal will have a mean value of 1/DCgain
+# times the mean of the input signal
+
+
+signal1_comp = applyInverseRCFilter(signal1, SR, filtertype, f_cut, order=1, DCgain=1)
+signal2_comp = applyInverseRCFilter(signal2, SR, filtertype, f_cut, order=1, DCgain=1)
+
+
+
+
+
[7]:
+
+
+
fig, axs = plt.subplots(2, 1)
+axs[0].plot(time, signal1, label="Input signal")
+axs[0].plot(time, signal1_comp, label="Compensated")
+axs[0].set_xlabel("Time (s)")
+axs[0].set_ylabel("Signal (a.u.)")
+axs[0].legend()
+
+axs[1].plot(time, signal2, label="Input signal")
+axs[1].plot(time, signal2_comp, label="Compensated")
+axs[1].set_xlabel("Time (s)")
+axs[1].set_ylabel("Signal (a.u.)")
+axs[1].legend()
+
+plt.tight_layout()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[8]:
+
+
+
# As a sanity-check, we may filter and compensate and compare
+#
+# Do we recover the original signals?
+
+signal1_check1 = applyRCFilter(signal1_comp, SR, filtertype, f_cut, order=1)
+signal1_check2 = applyInverseRCFilter(signal1_filt, SR, filtertype, f_cut, order=1)
+
+signal2_check1 = applyRCFilter(signal2_comp, SR, filtertype, f_cut, order=1)
+signal2_check2 = applyInverseRCFilter(signal2_filt, SR, filtertype, f_cut, order=1)
+
+fig, axs = plt.subplots(2, 1)
+axs[0].set_title("Signal 1")
+axs[0].plot(time, signal1_check1, label="Check 1")
+axs[0].plot(time, signal1_check2, label="Check 2")
+axs[0].legend()
+axs[0].set_xlabel("Time (s)")
+axs[0].set_ylabel("Signal (a. u.)")
+
+axs[1].set_title("Signal 2")
+axs[1].plot(time, signal2_check1, label="Check 1")
+axs[1].plot(time, signal2_check2, label="Check 2")
+axs[1].legend()
+axs[1].set_xlabel("Time (s)")
+axs[1].set_ylabel("Signal (a. u.)")
+
+plt.tight_layout()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

User-supplied transfer function

+

In an experimental situation, it is sometimes best to simply measure the transfer function of the transmission line and then apply (the inverse of) that transfer function to the system.

+

This is done with ripasso’s applyCustomTransferFunction.

+

In the following example, we invite the user to experiment with tf_noise_amp to see the drastic effect of low filter function values. In a real experiment, some care should be taken to provide a very smooth transfer function, either by heavy averaging or by curve fitting to a sane model (or both).

+
+
[9]:
+
+
+
# Lets first make some semi-realistic transfer function
+
+tf_points = 500
+
+tf_freq = np.linspace(0, 1000, tf_points)
+tf_amp = np.zeros(tf_points)
+tf_amp += np.hanning(2 * tf_points)[tf_points:][::-1]  # A high-pass
+tf_amp += (
+    0.1  # Note: a transfer function with very low values give unphysical compensation
+)
+tf_amp /= tf_amp.max()
+
+# and some experimental noise
+tf_noise_amp = 0.02
+tf_amp += (
+    np.convolve(0.02 * np.random.randn(tf_points), np.array([0.5, 1, 0.5]), mode="same")
+    / 2
+)
+
+# And then our favourite signal: the square wave
+
+sig_points = 1000
+signal = squarewave(sig_points, periods=10) + 0.01 * np.random.randn(1000)
+SR = 2000  # We pretend that the signal was sampled with this sample rate
+time = np.linspace(0, sig_points / SR, sig_points)
+
+# Then we may apply the filter we made
+
+signal_filtered = applyCustomTransferFunction(signal, SR, tf_freq, tf_amp)
+
+# Or conversely, we may apply its inverse
+
+signal_compensated = applyCustomTransferFunction(
+    signal, SR, tf_freq, tf_amp, invert=True
+)
+
+# Finally, it's nice to visualise things
+
+fig, axs = plt.subplots(2, 2)
+axs[0, 0].plot(tf_freq, tf_amp)
+axs[0, 0].set_xlabel("Freq. (Hz)")
+axs[0, 0].set_ylabel("Transfer func.")
+
+axs[0, 1].plot(time, signal, color="#ff9900")
+axs[0, 1].set_xlabel("Time (s)")
+axs[0, 1].set_ylabel("Sig. ampl. (arb. un.)")
+axs[0, 1].set_title("Input")
+
+axs[1, 0].plot(time, signal_filtered, color="#339966")
+axs[1, 0].set_xlabel("Time (s)")
+axs[1, 0].set_ylabel("Sig. ampl. (arb. un.)")
+axs[1, 0].set_title("Filtered")
+
+axs[1, 1].plot(time, signal_compensated, color="#990033")
+axs[1, 1].set_xlabel("Time (s)")
+axs[1, 1].set_ylabel("Sig.ampl. (arb. un.)")
+axs[1, 1].set_title("Compensated")
+
+plt.tight_layout()
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[ ]:
+
+
+

+
+
+
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/Filter_compensation.ipynb b/examples/Filter_compensation.ipynb new file mode 100644 index 000000000..9113473ad --- /dev/null +++ b/examples/Filter_compensation.ipynb @@ -0,0 +1,4416 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Filter compensation with the ripasso module\n", + "\n", + "The broadbean module gets a helping hand from the ripasso module when it needs to compensate for transfer functions of transmission lines.\n", + "\n", + "The ripasso module lets us apply filters and their inverses to signals.\n", + "\n", + "The module has two built-in types of filter, namely an RC-circuit high-pass and an RC-circuit low-pass. The transfer functions are\n", + "\n", + "$$\n", + "H_n(f)=\\left(\\frac{2\\pi f\\mathrm{i}\\tau}{1+2\\pi f\\mathrm{i}\\tau }\\right)^{n},\n", + "$$\n", + "for the nth order high-pass and\n", + "$$\n", + "L_n(f)=\\left(\\frac{1}{1+2\\pi f\\mathrm{i}\\tau }\\right)^{n},\n", + "$$\n", + "for the nth order low-pass. The parameter $\\tau$ is one over the cut-off frequency.\n", + "\n", + "The inversion (filter compensation) is performed by mulitplying with the inverse transfer function in frequency space and transforming back to the time domain, e.g. for a given signal $s(t)$ and a high-pass filter of order n,\n", + "\n", + "$$\n", + " s_\\text{filtered}(t) = \\mathcal{F}^{-1}[\\mathcal{F}[s](f)\\cdot H_n(f)](t)\n", + "$$\n", + "and\n", + "$$\n", + " s_\\text{compensated}(t) = \\mathcal{F}^{-1}[\\mathcal{F}[s](f)\\cdot H_{-n}(f)](t).\n", + "$$\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:16.503802Z", + "iopub.status.busy": "2025-01-21T06:30:16.503639Z", + "iopub.status.idle": "2025-01-21T06:30:16.876839Z", + "shell.execute_reply": "2025-01-21T06:30:16.876360Z" + } + }, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "from broadbean.ripasso import (\n", + " applyCustomTransferFunction,\n", + " applyInverseRCFilter,\n", + " applyRCFilter,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true, + "execution": { + "iopub.execute_input": "2025-01-21T06:30:16.878875Z", + "iopub.status.busy": "2025-01-21T06:30:16.878508Z", + "iopub.status.idle": "2025-01-21T06:30:16.882052Z", + "shell.execute_reply": "2025-01-21T06:30:16.881594Z" + } + }, + "outputs": [], + "source": [ + "def squarewave(npts, periods=5):\n", + " periods = int(periods)\n", + " array = np.zeros(npts)\n", + "\n", + " for n in range(periods):\n", + " array[int(n * npts / periods) : int((2 * n + 1) * npts / 2 / periods)] = 1\n", + "\n", + " return array" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## RC Filters" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:16.883770Z", + "iopub.status.busy": "2025-01-21T06:30:16.883606Z", + "iopub.status.idle": "2025-01-21T06:30:16.887490Z", + "shell.execute_reply": "2025-01-21T06:30:16.886931Z" + } + }, + "outputs": [], + "source": [ + "# Let's make some signals!\n", + "\n", + "SR = int(10e3)\n", + "npts = int(10e3)\n", + "\n", + "f_cut = 12 # filter cut-off frequency (Hz)\n", + "\n", + "time = np.linspace(0, npts / SR, npts)\n", + "T = time[-1]\n", + "\n", + "signal1 = np.sin(2 * np.pi * 5 / T * time) + 0.7 * np.cos(2 * np.pi * 1 / T * time)\n", + "signal2 = squarewave(npts, periods=4)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:16.889146Z", + "iopub.status.busy": "2025-01-21T06:30:16.888765Z", + "iopub.status.idle": "2025-01-21T06:30:16.893153Z", + "shell.execute_reply": "2025-01-21T06:30:16.892607Z" + } + }, + "outputs": [], + "source": [ + "# Then we may high-pass them or low-pass them\n", + "\n", + "filtertype = \"HP\"\n", + "# filtertype = 'LP'\n", + "\n", + "signal1_filt = applyRCFilter(signal1, SR, filtertype, f_cut, order=1)\n", + "signal2_filt = applyRCFilter(signal2, SR, filtertype, f_cut, order=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:16.894818Z", + "iopub.status.busy": "2025-01-21T06:30:16.894515Z", + "iopub.status.idle": "2025-01-21T06:30:16.983646Z", + "shell.execute_reply": "2025-01-21T06:30:16.983176Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# And inspect the result\n", + "\n", + "fig, axs = plt.subplots(2, 1)\n", + "axs[0].plot(time, signal1, label=\"Input signal\")\n", + "axs[0].plot(time, signal1_filt, label=\"Filtered\")\n", + "axs[0].set_xlabel(\"Time (s)\")\n", + "axs[0].set_ylabel(\"Signal (a.u.)\")\n", + "axs[0].legend()\n", + "\n", + "axs[1].plot(time, signal2, label=\"Input signal\")\n", + "axs[1].plot(time, signal2_filt, label=\"Filtered\")\n", + "axs[1].set_xlabel(\"Time (s)\")\n", + "axs[1].set_ylabel(\"Signal (a.u.)\")\n", + "axs[1].legend()\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": true, + "execution": { + "iopub.execute_input": "2025-01-21T06:30:16.985593Z", + "iopub.status.busy": "2025-01-21T06:30:16.985192Z", + "iopub.status.idle": "2025-01-21T06:30:16.989747Z", + "shell.execute_reply": "2025-01-21T06:30:16.989308Z" + } + }, + "outputs": [], + "source": [ + "# Similarly, we may pre-compensate for the filters\n", + "\n", + "# Note that we specify a DC gain for the inverse filters. This is because it is not possible\n", + "# to compensate for the true DC gain of zero of an RC high pass. Instead, we specify the\n", + "# gain of the high-pass filter at DC. The compensated signal will have a mean value of 1/DCgain\n", + "# times the mean of the input signal\n", + "\n", + "\n", + "signal1_comp = applyInverseRCFilter(signal1, SR, filtertype, f_cut, order=1, DCgain=1)\n", + "signal2_comp = applyInverseRCFilter(signal2, SR, filtertype, f_cut, order=1, DCgain=1)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:16.991448Z", + "iopub.status.busy": "2025-01-21T06:30:16.991121Z", + "iopub.status.idle": "2025-01-21T06:30:17.071247Z", + "shell.execute_reply": "2025-01-21T06:30:17.070810Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, axs = plt.subplots(2, 1)\n", + "axs[0].plot(time, signal1, label=\"Input signal\")\n", + "axs[0].plot(time, signal1_comp, label=\"Compensated\")\n", + "axs[0].set_xlabel(\"Time (s)\")\n", + "axs[0].set_ylabel(\"Signal (a.u.)\")\n", + "axs[0].legend()\n", + "\n", + "axs[1].plot(time, signal2, label=\"Input signal\")\n", + "axs[1].plot(time, signal2_comp, label=\"Compensated\")\n", + "axs[1].set_xlabel(\"Time (s)\")\n", + "axs[1].set_ylabel(\"Signal (a.u.)\")\n", + "axs[1].legend()\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:17.072871Z", + "iopub.status.busy": "2025-01-21T06:30:17.072697Z", + "iopub.status.idle": "2025-01-21T06:30:17.162382Z", + "shell.execute_reply": "2025-01-21T06:30:17.161788Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# As a sanity-check, we may filter and compensate and compare\n", + "#\n", + "# Do we recover the original signals?\n", + "\n", + "signal1_check1 = applyRCFilter(signal1_comp, SR, filtertype, f_cut, order=1)\n", + "signal1_check2 = applyInverseRCFilter(signal1_filt, SR, filtertype, f_cut, order=1)\n", + "\n", + "signal2_check1 = applyRCFilter(signal2_comp, SR, filtertype, f_cut, order=1)\n", + "signal2_check2 = applyInverseRCFilter(signal2_filt, SR, filtertype, f_cut, order=1)\n", + "\n", + "fig, axs = plt.subplots(2, 1)\n", + "axs[0].set_title(\"Signal 1\")\n", + "axs[0].plot(time, signal1_check1, label=\"Check 1\")\n", + "axs[0].plot(time, signal1_check2, label=\"Check 2\")\n", + "axs[0].legend()\n", + "axs[0].set_xlabel(\"Time (s)\")\n", + "axs[0].set_ylabel(\"Signal (a. u.)\")\n", + "\n", + "axs[1].set_title(\"Signal 2\")\n", + "axs[1].plot(time, signal2_check1, label=\"Check 1\")\n", + "axs[1].plot(time, signal2_check2, label=\"Check 2\")\n", + "axs[1].legend()\n", + "axs[1].set_xlabel(\"Time (s)\")\n", + "axs[1].set_ylabel(\"Signal (a. u.)\")\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## User-supplied transfer function\n", + "\n", + "In an experimental situation, it is sometimes best to simply _measure_ the transfer function of the transmission line and then apply (the inverse of) that transfer function to the system.\n", + "\n", + "This is done with ripasso's `applyCustomTransferFunction`.\n", + "\n", + "In the following example, we invite the user to experiment with `tf_noise_amp` to see the drastic effect of low filter function values. In a real experiment, some care should be taken to provide a very smooth transfer function, either by heavy averaging or by curve fitting to a sane model (or both)." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:17.164129Z", + "iopub.status.busy": "2025-01-21T06:30:17.163812Z", + "iopub.status.idle": "2025-01-21T06:30:17.289383Z", + "shell.execute_reply": "2025-01-21T06:30:17.288800Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Lets first make some semi-realistic transfer function\n", + "\n", + "tf_points = 500\n", + "\n", + "tf_freq = np.linspace(0, 1000, tf_points)\n", + "tf_amp = np.zeros(tf_points)\n", + "tf_amp += np.hanning(2 * tf_points)[tf_points:][::-1] # A high-pass\n", + "tf_amp += (\n", + " 0.1 # Note: a transfer function with very low values give unphysical compensation\n", + ")\n", + "tf_amp /= tf_amp.max()\n", + "\n", + "# and some experimental noise\n", + "tf_noise_amp = 0.02\n", + "tf_amp += (\n", + " np.convolve(0.02 * np.random.randn(tf_points), np.array([0.5, 1, 0.5]), mode=\"same\")\n", + " / 2\n", + ")\n", + "\n", + "# And then our favourite signal: the square wave\n", + "\n", + "sig_points = 1000\n", + "signal = squarewave(sig_points, periods=10) + 0.01 * np.random.randn(1000)\n", + "SR = 2000 # We pretend that the signal was sampled with this sample rate\n", + "time = np.linspace(0, sig_points / SR, sig_points)\n", + "\n", + "# Then we may apply the filter we made\n", + "\n", + "signal_filtered = applyCustomTransferFunction(signal, SR, tf_freq, tf_amp)\n", + "\n", + "# Or conversely, we may apply its inverse\n", + "\n", + "signal_compensated = applyCustomTransferFunction(\n", + " signal, SR, tf_freq, tf_amp, invert=True\n", + ")\n", + "\n", + "# Finally, it's nice to visualise things\n", + "\n", + "fig, axs = plt.subplots(2, 2)\n", + "axs[0, 0].plot(tf_freq, tf_amp)\n", + "axs[0, 0].set_xlabel(\"Freq. (Hz)\")\n", + "axs[0, 0].set_ylabel(\"Transfer func.\")\n", + "\n", + "axs[0, 1].plot(time, signal, color=\"#ff9900\")\n", + "axs[0, 1].set_xlabel(\"Time (s)\")\n", + "axs[0, 1].set_ylabel(\"Sig. ampl. (arb. un.)\")\n", + "axs[0, 1].set_title(\"Input\")\n", + "\n", + "axs[1, 0].plot(time, signal_filtered, color=\"#339966\")\n", + "axs[1, 0].set_xlabel(\"Time (s)\")\n", + "axs[1, 0].set_ylabel(\"Sig. ampl. (arb. un.)\")\n", + "axs[1, 0].set_title(\"Filtered\")\n", + "\n", + "axs[1, 1].plot(time, signal_compensated, color=\"#990033\")\n", + "axs[1, 1].set_xlabel(\"Time (s)\")\n", + "axs[1, 1].set_ylabel(\"Sig.ampl. (arb. un.)\")\n", + "axs[1, 1].set_title(\"Compensated\")\n", + "\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/Making_output_for_Tektronix_AWG70000A.html b/examples/Making_output_for_Tektronix_AWG70000A.html new file mode 100644 index 000000000..c688d7104 --- /dev/null +++ b/examples/Making_output_for_Tektronix_AWG70000A.html @@ -0,0 +1,1729 @@ + + + + + + + + + Introduction - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Introduction

+

The Tektronix AWG70000A series internally use .seqx files. The QCoDeS driver for the AWGs knows how to compile these files and broadbean knows what QCoDeS need to do so.

+
+

Step 1: Make a sequence

+
+
[1]:
+
+
+
# We make a sequence consisting of a bsic element with:
+# a wait time, a ramp up, a sine, a ramp down, and more wait time
+# And then we vary the sine frequency
+%matplotlib notebook
+import numpy as np
+
+import broadbean as bb
+from broadbean.plotting import plotter
+
+sine = bb.PulseAtoms.sine
+ramp = bb.PulseAtoms.ramp
+
+##########################
+# settings
+
+SR = 25e9
+t1 = 10e-9  # first wait time (s)
+ramp_time = 5e-9  # the ramp time (s)
+t_sine = 25e-9  # the sine time (s)
+high_level = 0.1  # the high level (V)
+sine_amp = 0.05  # the sine amplitude (V)
+t2 = 150e-9  # the second wait time (s)
+f0 = 1e9  # the base frequency of the sine (Hz)
+
+baseshape = bb.BluePrint()
+baseshape.insertSegment(0, ramp, (0, 0), dur=t1)
+baseshape.insertSegment(1, ramp, (0, high_level), dur=ramp_time)
+baseshape.insertSegment(
+    2, sine, (f0, sine_amp, high_level, 0), dur=t_sine, name="drive"
+)
+baseshape.insertSegment(3, ramp, (high_level, 0), dur=ramp_time)
+baseshape.insertSegment(4, ramp, (0, 0), dur=t2, name="wait")
+baseshape.setSegmentMarker("wait", (0, t_sine), 1)
+baseshape.setSegmentMarker("drive", (0, t_sine), 2)
+baseshape.setSR(SR)
+
+
+
+
+
[2]:
+
+
+
# Check that it looks the way we expect
+plotter(baseshape)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[3]:
+
+
+
# Make it into a sequence
+baseelem = bb.Element()
+baseelem.addBluePrint(1, baseshape)
+
+# Add elements and vary the sine freq.
+
+sine_freqs = np.linspace(1e9, 2e9, 5)
+
+seq = bb.Sequence()
+seq.setSR(SR)
+
+# We set each element of the sequence to wait for
+# a trigger from trigger input 'A'
+
+for index, freq in enumerate(sine_freqs):
+    elem = baseelem.copy()
+    elem.changeArg(1, "drive", "freq", freq)
+    seq.addElement(index + 1, elem)
+    seq.setSequencingTriggerWait(index + 1, 1)  # 1: trigA, 2: trigB, 3: EXT
+
+# and set the last element to point back to the first one
+seq.setSequencingGoto(index + 1, 1)
+
+seq.name = "tutorial_sequence"  # the sequence name will be needed later
+
+
+
+
+
+

Step 2: Initialise the driver

+
+
[4]:
+
+
+
# import and initialise the driver and ensure that the sample
+# rate and channel voltage is correct
+
+from qcodes.instrument_drivers.tektronix.AWG70002A import AWG70002A
+
+awg = AWG70002A("awg", "TCPIP0::172.20.2.243::inst0::INSTR")
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+ModuleNotFoundError                       Traceback (most recent call last)
+Cell In[4], line 4
+      1 # import and initialise the driver and ensure that the sample
+      2 # rate and channel voltage is correct
+----> 4 from qcodes.instrument_drivers.tektronix.AWG70002A import AWG70002A
+      6 awg = AWG70002A("awg", "TCPIP0::172.20.2.243::inst0::INSTR")
+
+ModuleNotFoundError: No module named 'qcodes'
+
+
+
+
[5]:
+
+
+
awg.sample_rate(SR)
+awg.ch1.awg_amplitude(0.5)  # this is the peak-to-peak amplitude of the channel
+
+# Now we must update the Sequence object with this information
+seq.setChannelAmplitude(1, awg.ch1.awg_amplitude())
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[5], line 1
+----> 1 awg.sample_rate(SR)
+      2 awg.ch1.awg_amplitude(0.5)  # this is the peak-to-peak amplitude of the channel
+      4 # Now we must update the Sequence object with this information
+
+NameError: name 'awg' is not defined
+
+
+
+
+

Step 3: Make output for the driver

+
+
[6]:
+
+
+
# If the sequence has been built correctly, it's a one-liner
+seqx_input = seq.outputForSEQXFile()
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+KeyError                                  Traceback (most recent call last)
+Cell In[6], line 2
+      1 # If the sequence has been built correctly, it's a one-liner
+----> 2 seqx_input = seq.outputForSEQXFile()
+
+File /opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:988, in Sequence.outputForSEQXFile(self)
+    971 """
+    972 Generate a tuple matching the call signature of the QCoDeS
+    973 AWG70000A driver's `makeSEQXFile` function. If channel delays
+   (...)
+    984         go_to, wfms, amplitudes, seqname)
+    985 """
+    987 # most of the footwork is done by the following function
+--> 988 elements = self._prepareForOutputting()
+    989 # _prepareForOutputting asserts that channel amplitudes and
+    990 # full sequencing is specified
+    991 seqlen = len(elements)
+
+File /opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:882, in Sequence._prepareForOutputting(self)
+    880     ampkey = f"channel{chan}_amplitude"
+    881     if ampkey not in self._awgspecs.keys():
+--> 882         raise KeyError(
+    883             "No amplitude specified for channel {chan}. Can not continue."
+    884         )
+    886 # Apply channel delays.
+    887 delays = []
+
+KeyError: 'No amplitude specified for channel {chan}. Can not continue.'
+
+
+
+
+

Step 4: Build, send, load, and assign a .seqx file

+
+
[7]:
+
+
+
# compile a binary .seqx file
+seqx_output = awg.makeSEQXFile(*seqx_input)
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[7], line 2
+      1 # compile a binary .seqx file
+----> 2 seqx_output = awg.makeSEQXFile(*seqx_input)
+
+NameError: name 'awg' is not defined
+
+
+
+
[8]:
+
+
+
# transfer it to the awg harddrive
+awg.sendSEQXFile(seqx_output, "tutorial.seqx")
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[8], line 2
+      1 # transfer it to the awg harddrive
+----> 2 awg.sendSEQXFile(seqx_output, "tutorial.seqx")
+
+NameError: name 'awg' is not defined
+
+
+
+
[9]:
+
+
+
# load it into awg active memory
+awg.loadSEQXFile("tutorial.seqx")
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[9], line 2
+      1 # load it into awg active memory
+----> 2 awg.loadSEQXFile("tutorial.seqx")
+
+NameError: name 'awg' is not defined
+
+
+
+
[10]:
+
+
+
# assign tracks from the sequence to the awg sequencer
+awg.ch1.setSequenceTrack("tutorial_sequence", 1)
+
+# NB: Each channel has an assigned resolution, either 8, 9, or 10.
+# 8 means 8 bits for the waveform, 2 bits for the markers (i.e. 2 markers)
+# 9 means 9 bits for the waveform, 1 bit for the markers (i.e. 1 marker)
+# 10 means 10 bit for the waveform, no markers
+#
+# Since we want to have two markers, we make sure that the resolution is 8
+awg.ch1.resolution(8)
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[10], line 2
+      1 # assign tracks from the sequence to the awg sequencer
+----> 2 awg.ch1.setSequenceTrack("tutorial_sequence", 1)
+      4 # NB: Each channel has an assigned resolution, either 8, 9, or 10.
+      5 # 8 means 8 bits for the waveform, 2 bits for the markers (i.e. 2 markers)
+      6 # 9 means 9 bits for the waveform, 1 bit for the markers (i.e. 1 marker)
+      7 # 10 means 10 bit for the waveform, no markers
+      8 #
+      9 # Since we want to have two markers, we make sure that the resolution is 8
+     10 awg.ch1.resolution(8)
+
+NameError: name 'awg' is not defined
+
+
+
+
+

Step 5: Play it

+
+
[11]:
+
+
+
awg.ch1.state(1)
+awg.play()
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[11], line 1
+----> 1 awg.ch1.state(1)
+      2 awg.play()
+
+NameError: name 'awg' is not defined
+
+
+
+
[12]:
+
+
+
# force trigger the instrument to play the next part of the sequence
+awg.force_triggerA()
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[12], line 2
+      1 # force trigger the instrument to play the next part of the sequence
+----> 2 awg.force_triggerA()
+
+NameError: name 'awg' is not defined
+
+
+
+
[13]:
+
+
+
# eventually shut down and turn off
+awg.stop()
+awg.ch1.state(0)
+awg.close()
+
+
+
+
+
+
+
+
+---------------------------------------------------------------------------
+NameError                                 Traceback (most recent call last)
+Cell In[13], line 2
+      1 # eventually shut down and turn off
+----> 2 awg.stop()
+      3 awg.ch1.state(0)
+      4 awg.close()
+
+NameError: name 'awg' is not defined
+
+
+
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/Making_output_for_Tektronix_AWG70000A.ipynb b/examples/Making_output_for_Tektronix_AWG70000A.ipynb new file mode 100644 index 000000000..13d98b3b8 --- /dev/null +++ b/examples/Making_output_for_Tektronix_AWG70000A.ipynb @@ -0,0 +1,1495 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Introduction\n", + "\n", + "The Tektronix AWG70000A series internally use `.seqx` files. The QCoDeS driver for the AWGs knows how to compile these files and broadbean knows what QCoDeS need to do so. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 1: Make a sequence" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:18.736484Z", + "iopub.status.busy": "2025-01-21T06:30:18.736067Z", + "iopub.status.idle": "2025-01-21T06:30:19.116582Z", + "shell.execute_reply": "2025-01-21T06:30:19.116082Z" + } + }, + "outputs": [], + "source": [ + "# We make a sequence consisting of a bsic element with:\n", + "# a wait time, a ramp up, a sine, a ramp down, and more wait time\n", + "# And then we vary the sine frequency\n", + "%matplotlib notebook\n", + "import numpy as np\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "sine = bb.PulseAtoms.sine\n", + "ramp = bb.PulseAtoms.ramp\n", + "\n", + "##########################\n", + "# settings\n", + "\n", + "SR = 25e9\n", + "t1 = 10e-9 # first wait time (s)\n", + "ramp_time = 5e-9 # the ramp time (s)\n", + "t_sine = 25e-9 # the sine time (s)\n", + "high_level = 0.1 # the high level (V)\n", + "sine_amp = 0.05 # the sine amplitude (V)\n", + "t2 = 150e-9 # the second wait time (s)\n", + "f0 = 1e9 # the base frequency of the sine (Hz)\n", + "\n", + "baseshape = bb.BluePrint()\n", + "baseshape.insertSegment(0, ramp, (0, 0), dur=t1)\n", + "baseshape.insertSegment(1, ramp, (0, high_level), dur=ramp_time)\n", + "baseshape.insertSegment(\n", + " 2, sine, (f0, sine_amp, high_level, 0), dur=t_sine, name=\"drive\"\n", + ")\n", + "baseshape.insertSegment(3, ramp, (high_level, 0), dur=ramp_time)\n", + "baseshape.insertSegment(4, ramp, (0, 0), dur=t2, name=\"wait\")\n", + "baseshape.setSegmentMarker(\"wait\", (0, t_sine), 1)\n", + "baseshape.setSegmentMarker(\"drive\", (0, t_sine), 2)\n", + "baseshape.setSR(SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.118524Z", + "iopub.status.busy": "2025-01-21T06:30:19.118283Z", + "iopub.status.idle": "2025-01-21T06:30:19.141605Z", + "shell.execute_reply": "2025-01-21T06:30:19.141020Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Check that it looks the way we expect\n", + "plotter(baseshape)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.143300Z", + "iopub.status.busy": "2025-01-21T06:30:19.142996Z", + "iopub.status.idle": "2025-01-21T06:30:19.148364Z", + "shell.execute_reply": "2025-01-21T06:30:19.147855Z" + } + }, + "outputs": [], + "source": [ + "# Make it into a sequence\n", + "baseelem = bb.Element()\n", + "baseelem.addBluePrint(1, baseshape)\n", + "\n", + "# Add elements and vary the sine freq.\n", + "\n", + "sine_freqs = np.linspace(1e9, 2e9, 5)\n", + "\n", + "seq = bb.Sequence()\n", + "seq.setSR(SR)\n", + "\n", + "# We set each element of the sequence to wait for\n", + "# a trigger from trigger input 'A'\n", + "\n", + "for index, freq in enumerate(sine_freqs):\n", + " elem = baseelem.copy()\n", + " elem.changeArg(1, \"drive\", \"freq\", freq)\n", + " seq.addElement(index + 1, elem)\n", + " seq.setSequencingTriggerWait(index + 1, 1) # 1: trigA, 2: trigB, 3: EXT\n", + "\n", + "# and set the last element to point back to the first one\n", + "seq.setSequencingGoto(index + 1, 1)\n", + "\n", + "seq.name = \"tutorial_sequence\" # the sequence name will be needed later" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 2: Initialise the driver" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.150114Z", + "iopub.status.busy": "2025-01-21T06:30:19.149666Z", + "iopub.status.idle": "2025-01-21T06:30:19.294383Z", + "shell.execute_reply": "2025-01-21T06:30:19.293789Z" + } + }, + "outputs": [ + { + "ename": "ModuleNotFoundError", + "evalue": "No module named 'qcodes'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mModuleNotFoundError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[4], line 4\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# import and initialise the driver and ensure that the sample\u001b[39;00m\n\u001b[1;32m 2\u001b[0m \u001b[38;5;66;03m# rate and channel voltage is correct\u001b[39;00m\n\u001b[0;32m----> 4\u001b[0m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01mqcodes\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01minstrument_drivers\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mtektronix\u001b[39;00m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mAWG70002A\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m AWG70002A\n\u001b[1;32m 6\u001b[0m awg \u001b[38;5;241m=\u001b[39m AWG70002A(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mawg\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mTCPIP0::172.20.2.243::inst0::INSTR\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mModuleNotFoundError\u001b[0m: No module named 'qcodes'" + ] + } + ], + "source": [ + "# import and initialise the driver and ensure that the sample\n", + "# rate and channel voltage is correct\n", + "\n", + "from qcodes.instrument_drivers.tektronix.AWG70002A import AWG70002A\n", + "\n", + "awg = AWG70002A(\"awg\", \"TCPIP0::172.20.2.243::inst0::INSTR\")" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.296388Z", + "iopub.status.busy": "2025-01-21T06:30:19.296039Z", + "iopub.status.idle": "2025-01-21T06:30:19.307568Z", + "shell.execute_reply": "2025-01-21T06:30:19.307086Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[5], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39msample_rate(SR)\n\u001b[1;32m 2\u001b[0m awg\u001b[38;5;241m.\u001b[39mch1\u001b[38;5;241m.\u001b[39mawg_amplitude(\u001b[38;5;241m0.5\u001b[39m) \u001b[38;5;66;03m# this is the peak-to-peak amplitude of the channel\u001b[39;00m\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m# Now we must update the Sequence object with this information\u001b[39;00m\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "awg.sample_rate(SR)\n", + "awg.ch1.awg_amplitude(0.5) # this is the peak-to-peak amplitude of the channel\n", + "\n", + "# Now we must update the Sequence object with this information\n", + "seq.setChannelAmplitude(1, awg.ch1.awg_amplitude())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 3: Make output for the driver" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.309269Z", + "iopub.status.busy": "2025-01-21T06:30:19.308940Z", + "iopub.status.idle": "2025-01-21T06:30:19.384690Z", + "shell.execute_reply": "2025-01-21T06:30:19.384227Z" + } + }, + "outputs": [ + { + "ename": "KeyError", + "evalue": "'No amplitude specified for channel {chan}. Can not continue.'", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mKeyError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[6], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# If the sequence has been built correctly, it's a one-liner\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m seqx_input \u001b[38;5;241m=\u001b[39m \u001b[43mseq\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43moutputForSEQXFile\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n", + "File \u001b[0;32m/opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:988\u001b[0m, in \u001b[0;36mSequence.outputForSEQXFile\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 971\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 972\u001b[0m \u001b[38;5;124;03mGenerate a tuple matching the call signature of the QCoDeS\u001b[39;00m\n\u001b[1;32m 973\u001b[0m \u001b[38;5;124;03mAWG70000A driver's `makeSEQXFile` function. If channel delays\u001b[39;00m\n\u001b[0;32m (...)\u001b[0m\n\u001b[1;32m 984\u001b[0m \u001b[38;5;124;03m go_to, wfms, amplitudes, seqname)\u001b[39;00m\n\u001b[1;32m 985\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m 987\u001b[0m \u001b[38;5;66;03m# most of the footwork is done by the following function\u001b[39;00m\n\u001b[0;32m--> 988\u001b[0m elements \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_prepareForOutputting\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n\u001b[1;32m 989\u001b[0m \u001b[38;5;66;03m# _prepareForOutputting asserts that channel amplitudes and\u001b[39;00m\n\u001b[1;32m 990\u001b[0m \u001b[38;5;66;03m# full sequencing is specified\u001b[39;00m\n\u001b[1;32m 991\u001b[0m seqlen \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mlen\u001b[39m(elements)\n", + "File \u001b[0;32m/opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:882\u001b[0m, in \u001b[0;36mSequence._prepareForOutputting\u001b[0;34m(self)\u001b[0m\n\u001b[1;32m 880\u001b[0m ampkey \u001b[38;5;241m=\u001b[39m \u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mchannel\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mchan\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m_amplitude\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 881\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ampkey \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_awgspecs\u001b[38;5;241m.\u001b[39mkeys():\n\u001b[0;32m--> 882\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mKeyError\u001b[39;00m(\n\u001b[1;32m 883\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mNo amplitude specified for channel \u001b[39m\u001b[38;5;132;01m{chan}\u001b[39;00m\u001b[38;5;124m. Can not continue.\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[1;32m 884\u001b[0m )\n\u001b[1;32m 886\u001b[0m \u001b[38;5;66;03m# Apply channel delays.\u001b[39;00m\n\u001b[1;32m 887\u001b[0m delays \u001b[38;5;241m=\u001b[39m []\n", + "\u001b[0;31mKeyError\u001b[0m: 'No amplitude specified for channel {chan}. Can not continue.'" + ] + } + ], + "source": [ + "# If the sequence has been built correctly, it's a one-liner\n", + "seqx_input = seq.outputForSEQXFile()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 4: Build, send, load, and assign a .seqx file" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.386392Z", + "iopub.status.busy": "2025-01-21T06:30:19.386040Z", + "iopub.status.idle": "2025-01-21T06:30:19.396284Z", + "shell.execute_reply": "2025-01-21T06:30:19.395848Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[7], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# compile a binary .seqx file\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m seqx_output \u001b[38;5;241m=\u001b[39m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mmakeSEQXFile(\u001b[38;5;241m*\u001b[39mseqx_input)\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# compile a binary .seqx file\n", + "seqx_output = awg.makeSEQXFile(*seqx_input)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.398013Z", + "iopub.status.busy": "2025-01-21T06:30:19.397682Z", + "iopub.status.idle": "2025-01-21T06:30:19.407897Z", + "shell.execute_reply": "2025-01-21T06:30:19.407481Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[8], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# transfer it to the awg harddrive\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39msendSEQXFile(seqx_output, \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtutorial.seqx\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# transfer it to the awg harddrive\n", + "awg.sendSEQXFile(seqx_output, \"tutorial.seqx\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.409318Z", + "iopub.status.busy": "2025-01-21T06:30:19.409171Z", + "iopub.status.idle": "2025-01-21T06:30:19.419554Z", + "shell.execute_reply": "2025-01-21T06:30:19.418982Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[9], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# load it into awg active memory\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mloadSEQXFile(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtutorial.seqx\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# load it into awg active memory\n", + "awg.loadSEQXFile(\"tutorial.seqx\")" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": true, + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.421270Z", + "iopub.status.busy": "2025-01-21T06:30:19.420948Z", + "iopub.status.idle": "2025-01-21T06:30:19.466604Z", + "shell.execute_reply": "2025-01-21T06:30:19.466000Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[10], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# assign tracks from the sequence to the awg sequencer\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mch1\u001b[38;5;241m.\u001b[39msetSequenceTrack(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtutorial_sequence\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;241m1\u001b[39m)\n\u001b[1;32m 4\u001b[0m \u001b[38;5;66;03m# NB: Each channel has an assigned resolution, either 8, 9, or 10.\u001b[39;00m\n\u001b[1;32m 5\u001b[0m \u001b[38;5;66;03m# 8 means 8 bits for the waveform, 2 bits for the markers (i.e. 2 markers)\u001b[39;00m\n\u001b[1;32m 6\u001b[0m \u001b[38;5;66;03m# 9 means 9 bits for the waveform, 1 bit for the markers (i.e. 1 marker)\u001b[39;00m\n\u001b[1;32m 7\u001b[0m \u001b[38;5;66;03m# 10 means 10 bit for the waveform, no markers\u001b[39;00m\n\u001b[1;32m 8\u001b[0m \u001b[38;5;66;03m#\u001b[39;00m\n\u001b[1;32m 9\u001b[0m \u001b[38;5;66;03m# Since we want to have two markers, we make sure that the resolution is 8\u001b[39;00m\n\u001b[1;32m 10\u001b[0m awg\u001b[38;5;241m.\u001b[39mch1\u001b[38;5;241m.\u001b[39mresolution(\u001b[38;5;241m8\u001b[39m)\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# assign tracks from the sequence to the awg sequencer\n", + "awg.ch1.setSequenceTrack(\"tutorial_sequence\", 1)\n", + "\n", + "# NB: Each channel has an assigned resolution, either 8, 9, or 10.\n", + "# 8 means 8 bits for the waveform, 2 bits for the markers (i.e. 2 markers)\n", + "# 9 means 9 bits for the waveform, 1 bit for the markers (i.e. 1 marker)\n", + "# 10 means 10 bit for the waveform, no markers\n", + "#\n", + "# Since we want to have two markers, we make sure that the resolution is 8\n", + "awg.ch1.resolution(8)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Step 5: Play it" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.468467Z", + "iopub.status.busy": "2025-01-21T06:30:19.468053Z", + "iopub.status.idle": "2025-01-21T06:30:19.479056Z", + "shell.execute_reply": "2025-01-21T06:30:19.478539Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[11], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mch1\u001b[38;5;241m.\u001b[39mstate(\u001b[38;5;241m1\u001b[39m)\n\u001b[1;32m 2\u001b[0m awg\u001b[38;5;241m.\u001b[39mplay()\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "awg.ch1.state(1)\n", + "awg.play()" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": true, + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.480725Z", + "iopub.status.busy": "2025-01-21T06:30:19.480397Z", + "iopub.status.idle": "2025-01-21T06:30:19.490636Z", + "shell.execute_reply": "2025-01-21T06:30:19.490072Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[12], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# force trigger the instrument to play the next part of the sequence\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mforce_triggerA()\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# force trigger the instrument to play the next part of the sequence\n", + "awg.force_triggerA()" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": true, + "execution": { + "iopub.execute_input": "2025-01-21T06:30:19.492445Z", + "iopub.status.busy": "2025-01-21T06:30:19.492110Z", + "iopub.status.idle": "2025-01-21T06:30:19.502588Z", + "shell.execute_reply": "2025-01-21T06:30:19.502108Z" + } + }, + "outputs": [ + { + "ename": "NameError", + "evalue": "name 'awg' is not defined", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mNameError\u001b[0m Traceback (most recent call last)", + "Cell \u001b[0;32mIn[13], line 2\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[38;5;66;03m# eventually shut down and turn off\u001b[39;00m\n\u001b[0;32m----> 2\u001b[0m \u001b[43mawg\u001b[49m\u001b[38;5;241m.\u001b[39mstop()\n\u001b[1;32m 3\u001b[0m awg\u001b[38;5;241m.\u001b[39mch1\u001b[38;5;241m.\u001b[39mstate(\u001b[38;5;241m0\u001b[39m)\n\u001b[1;32m 4\u001b[0m awg\u001b[38;5;241m.\u001b[39mclose()\n", + "\u001b[0;31mNameError\u001b[0m: name 'awg' is not defined" + ] + } + ], + "source": [ + "# eventually shut down and turn off\n", + "awg.stop()\n", + "awg.ch1.state(0)\n", + "awg.close()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/Pulse_Building_Tutorial.html b/examples/Pulse_Building_Tutorial.html new file mode 100644 index 000000000..bcaf04d32 --- /dev/null +++ b/examples/Pulse_Building_Tutorial.html @@ -0,0 +1,1263 @@ + + + + + + + + + Pulsebuilding tutorial - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Pulsebuilding tutorial

+
+

Table of Contents

+ +
+
+

Lingo

+

Let’s settle on a vocabulary. At the highest level, we construct sequences. These sequences will eventually be uploaded to an AWG, e.g. the Tektronix AWG 5014. Each sequence consists of several elements than again consist of a number of channels. On each channel reside a waveform and two markers. The waveform and markers may either be added as numpy arrays or as blueprint. A blueprint is a set of instructions for making a waveform and two markers and consists of several +segments

+

That is to say, the food chain goes: segment -> blueprint -> element -> sequence.

+
+

Segments:

+
+

Intro to Normal segments:

+

A normal segment consists of a unique name, a function object, a tuple of arguments to the function, and a duration.

+
    +
  • The name: can be provided by the user or omitted. If omitted, the segment will get the name of its function. Since all names must be unique, the blueprint appends numbers to names if they occur more than once. The numbers are appended chronologically throughout the blueprint. See example below. Note that valid input (base) names are strings NOT ending in a number. Thus, ‘pi/2pulse’ is valid, whereas ‘pulsepi/2’ is not.

  • +
  • The function: must be a python function taking at least two arguments; the sample rate and the segment duration. If the function takes other arguments (such as ramp slope, frequency, etc.) sample rate and duration arguments must be the last positional arguments. Keyword arguments are currently not allowed. See example at the very end.

  • +
  • The arguments: are in a tuple of \(n-2\) arguments for a function taking \(n\) arguments, i.e. specifying everything but the sample rate and duration.

  • +
  • The duration is a single number giving the desired duration of the segment in seconds. Some responsibility for making this number sensible with respect to the sample rate rests on the user.

  • +
+
+
+

Intro to Special segments:

+

A special segment has a (protected) name and a number of arguments. So far, two special segments exist.

+
    +
  • waituntil, args [time (int)]: When put in a blueprint, this function ensures that the next segment starts at the absolute time time after the start of the element. It does so by filling any excess time with zeros. It fails if the previous segment will finish after time time.

  • +
  • makemeanfit. Not implemented yet. Will make the mean of the blueprint be a specified number. Will (eventually) exist in several versions, e.g. one achieving the goal by adding an offset, another by adding an appropriate DC segment at the end of the blueprint.

  • +
+
+
+
+

Intro to Blueprints:

+

Consist of a number of segments. Has an associated sample rate.

+
+
+

Intro to Elements:

+

Has a number of blueprints on each channel. Can have an arbitraty amount of integer-indexed channels, but the blueprint on each channel must have the same number of points and the same total duration as all the other blueprints.

+
+
+

Intro to Sequences:

+

Have an associated sample rate.

+
+
[1]:
+
+
+
#
+# IMPORTS
+#
+%matplotlib inline
+import matplotlib as mpl
+import numpy as np
+
+import broadbean as bb
+from broadbean.plotting import plotter
+
+mpl.rcParams["figure.figsize"] = (8, 3)
+mpl.rcParams["figure.subplot.bottom"] = 0.15
+
+
+
+
+
+
+
+

Blueprints

+
+

Basic blueprinting

+

(back to ToC)

+

In this section we show how to construct basic blueprints. The units of the vertical axis is volts and the units of the horizontal axis (the durations) is seconds.

+
+
[2]:
+
+
+
# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.
+ramp = bb.PulseAtoms.ramp  # args: start, stop
+sine = bb.PulseAtoms.sine  # args: freq, ampl, off, phase
+arb_func = bb.PulseAtoms.arb_func  # args provided in a dict
+
+# make a blueprint
+
+# The blueprint takes no arguments
+bp1 = bb.BluePrint()  # Do-nothing initialisation
+
+# the blueprint is filled via the insertSegment method
+# Call signature: position in the blueprint, function, args, name, duration
+bp1.insertSegment(0, ramp, (0, 1e-3), name="", dur=3e-6)
+
+# A sample rate can be set (Sa/S). Without a sample rate, we can not plot the blueprint.
+bp1.setSR(1e9)
+
+# The blueprint can be inspected. Note that the segment was auto-named 'ramp'
+bp1.showPrint()
+
+# more segments can be added...
+bp1.insertSegment(1, sine, (5e5, 1e-3, 1e-3, 0), name="mysine", dur=2e-6)
+bp1.insertSegment(2, ramp, (1e-3, 0), name="", dur=3e-6)
+bp1.insertSegment(
+    3, arb_func, (lambda t, ampl: ampl * t * t, {"ampl": 5e8}), name="myfunc", dur=2e-6
+)
+
+# ... and reinspected
+bp1.showPrint()
+
+
+
+
+
+
+
+
+Legend: Name, function, arguments, timesteps, durations
+Segment 1: "ramp", PulseAtoms.ramp, (0, 0.001), 3e-06
+----------
+Legend: Name, function, arguments, timesteps, durations
+Segment 1: "ramp", PulseAtoms.ramp, (0, 0.001), 3e-06
+Segment 2: "mysine", PulseAtoms.sine, (500000.0, 0.001, 0.001, 0), 2e-06
+Segment 3: "ramp2", PulseAtoms.ramp, (0.001, 0), 3e-06
+Segment 4: "myfunc", PulseAtoms.arb_func, (<function <lambda> at 0x7f75b0a63f60>, {'ampl': 500000000.0}), 2e-06
+----------
+
+
+
+
[3]:
+
+
+
# For easy overview, we may plot the blueprint
+plotter(bp1)
+
+# The two bleak lines (red, blue) above the graph represent the channel markers.
+# They are described below.
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_4_0.png +
+
+
+
[4]:
+
+
+
# Blueprints may be added together
+bp2 = bp1 + bp1
+plotter(bp2)
+
+# Segments may be removed from a blueprint. They are removed by name.
+bp2.removeSegment("ramp2")
+plotter(bp2)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_5_0.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_5_1.png +
+
+
+
[5]:
+
+
+
# A blueprint has a handful of different lengths one may check
+print(f"Number of points in blueprint: {bp1.points}")
+print(f"Length of blueprint in seconds: {bp1.duration}")
+print(f"Number of segments in blueprint: {bp1.length_segments}")
+
+
+
+
+
+
+
+
+Number of points in blueprint: 10000
+Length of blueprint in seconds: 9.999999999999999e-06
+Number of segments in blueprint: 4
+
+
+
+
+

Markers

+

(back to ToC)

+

All markers are OFF by default. Markers can be added to a blueprint (switched ON) in two different ways. Either a marker is specified by its ON time in absolute time or by its ON time relative to a certain segment.

+
+
[6]:
+
+
+
# Absolute time marker specification
+
+# The blueprint has a list of tuples for each marker. The tuples are (switch_on_time, duration)
+
+# create a blueprint
+bp_atm = bb.BluePrint()
+bp_atm.setSR(100)
+bp_atm.insertSegment(0, ramp, (0, 1), dur=3)
+bp_atm.insertSegment(1, sine, (0.5, 1, 1, 0), dur=2)
+bp_atm.insertSegment(2, ramp, (1, 0), dur=3)
+
+# specify markers in absolute time
+bp_atm.marker1 = [(1, 0.5), (2, 0.5)]
+bp_atm.marker2 = [(1.5, 0.2), (2.5, 0.1)]
+
+plotter(bp_atm)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_8_0.png +
+
+
+
[7]:
+
+
+
# Relative time marker specification
+
+bp_rtm = bb.BluePrint()
+bp_rtm.setSR(100)
+bp_rtm.insertSegment(0, ramp, (0, 1), dur=1)
+bp_rtm.insertSegment(1, ramp, (1, 1), dur=1)
+bp_rtm.insertSegment(
+    2, sine, (1.675, 1, 0, np.pi / 2), dur=1.5, name="mysine"
+)  # This is the important segment
+# make marker 1 go ON a bit before the sine comes on
+bp_rtm.setSegmentMarker(
+    "mysine", (-0.1, 0.2), 1
+)  # segment name, (delay, duration), markerID
+# make marker 2 go ON halfway through the sine
+bp_rtm.setSegmentMarker("mysine", (0.75, 0.1), 2)
+
+plotter(bp_rtm)
+
+# Even if we insert segments before and after the sine, the markers "stick" to the sine segment
+bp_rtm.insertSegment(0, ramp, (0, 0), dur=1)
+bp_rtm.insertSegment(-1, ramp, (0, 0.2), dur=1)
+
+plotter(bp_rtm)
+
+
+# NB: the two different ways of inputting markers will never directly conflict, since one only specifies when to turn
+# markers ON. It is up to the user to ensure that markers switch off again as expected, i.e. that different marker
+# specifications do not overlap.
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_9_0.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_9_1.png +
+
+
+
+

Modifying blueprints

+

(back to ToC)

+
+
[8]:
+
+
+
# An essential feature of blueprints is that they can be modified
+
+bp_mod = bb.BluePrint()
+bp_mod.setSR(100)
+
+bp_mod.insertSegment(0, ramp, (0, 0), name="before", dur=1)
+bp_mod.insertSegment(1, ramp, (1, 1), name="plateau", dur=1)
+bp_mod.insertSegment(2, ramp, (0, 0), name="after", dur=1)
+
+plotter(bp_mod)
+
+# Functional arguments can be changed
+
+# They are looked up by segment name
+bp_mod.changeArg(
+    "before", "stop", 1
+)  # the argument to change may either be the argument name or its position
+bp_mod.changeArg("after", 0, 1)
+
+plotter(bp_mod)
+
+# Durations can also be changed
+bp_mod.changeDuration("plateau", 2)
+
+plotter(bp_mod)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_11_0.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_11_1.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_11_2.png +
+
+
+
+

Special segments

+

(back to ToC)

+
+
[9]:
+
+
+
# The 'waituntil' segment fills up a part of the blueprint with zeros
+
+# Example: a square pulse, then waiting until 5 s exactly and then a new sine
+
+bp_wait = bb.BluePrint()
+bp_wait.setSR(100)
+
+bp_wait.insertSegment(0, ramp, (0, 0), dur=1)
+bp_wait.insertSegment(1, ramp, (1, 1), name="plateau", dur=1)
+# function must be sthe string 'waituntil', the argument is the ABSOLUTE time to wait until
+bp_wait.insertSegment(2, "waituntil", (5,))
+bp_wait.insertSegment(3, sine, (1, 0.1, 0, -np.pi / 2), dur=1)
+plotter(bp_wait)
+
+# If we make the square pulse longer, the sine still occurs at 5 s
+bp_wait.changeDuration("plateau", 1.5)
+plotter(bp_wait)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_13_0.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_13_1.png +
+
+
+
+
+

Elements

+

(back to ToC)

+

Elements are containers containing blueprints. A valid element consists of blueprints that all have the same number of points and the same overall duration.

+
+
[10]:
+
+
+
# Example 1
+
+# Create the blueprints
+bp_square = bb.BluePrint()
+bp_square.setSR(1e9)
+bp_square.insertSegment(0, ramp, (0, 0), dur=0.1e-6)
+bp_square.insertSegment(1, ramp, (10e-3, 10e-3), name="top", dur=0.1e-6)
+bp_square.insertSegment(2, ramp, (0, 0), dur=0.1e-6)
+bp_boxes = bp_square + bp_square
+#
+bp_sine = bb.BluePrint()
+bp_sine.setSR(1e9)
+bp_sine.insertSegment(0, sine, (3.333e6, 25e-3, 0, 0), dur=0.3e-6)
+bp_sineandboxes = bp_sine + bp_square
+
+# Now we create an element and add the blueprints to channel 1 and 3, respectively
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp_boxes)
+elem1.addBluePrint(3, bp_sineandboxes)
+
+# We can check the validity of the element
+elem1.validateDurations()  # raises an ElementDurationError if something is wrong. If all is OK, does nothing.
+
+# And we can plot the element
+plotter(elem1)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_15_0.png +
+
+
+
[11]:
+
+
+
# An element has several features
+print(f"Designated channels: {elem1.channels}")
+print(f"Total duration: {elem1.duration} s")
+print(f"Sample rate: {elem1.SR} (Sa/S)")
+
+
+
+
+
+
+
+
+Designated channels: [1, 3]
+Total duration: 6e-07 s
+Sample rate: 1000000000.0 (Sa/S)
+
+
+
+
[12]:
+
+
+
# We can modify the blueprints of an element through the element object
+
+# Change the sine freq
+elem1.changeArg(
+    3, "sine", "freq", 6.67e6
+)  # Call signature: channel, segment name, argument, new_value
+
+# make the second plateaus last longer
+elem1.changeDuration(
+    1, "top2", 0.2e-6
+)  # In this blueprint, the second top is called top2
+elem1.changeDuration(3, "top", 0.2e-6)
+
+plotter(elem1)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_17_0.png +
+
+
+
+

Sequences

+

(back to ToC)

+

Finally, we have reached the top level of the module: sequences. Unsurprisingly, sequences are containers containing elements. All elements in a sequence must specify the same channels.

+
+
[13]:
+
+
+
seq1 = bb.Sequence()
+
+# We fill up the sequence by adding elements at different sequence positions.
+# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3
+
+#
+# Make blueprints, make elements
+
+# Create the blueprints
+bp_square = bb.BluePrint()
+bp_square.setSR(1e9)
+bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)
+bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name="top", dur=100e-9)
+bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)
+bp_boxes = bp_square + bp_square
+#
+bp_sine = bb.BluePrint()
+bp_sine.setSR(1e9)
+bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)
+bp_sineandboxes = bp_sine + bp_square
+
+# create elements
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp_boxes)
+elem1.addBluePrint(3, bp_sineandboxes)
+#
+elem2 = bb.Element()
+elem2.addBluePrint(3, bp_boxes)
+elem2.addBluePrint(1, bp_sineandboxes)
+
+# Fill up the sequence
+seq1.addElement(1, elem1)  # Call signature: seq. pos., element
+seq1.addElement(2, elem2)
+seq1.addElement(3, elem1)
+
+# set its sample rate
+seq1.setSR(elem1.SR)
+
+
+
+
+
[14]:
+
+
+
help(seq1.element(1).changeArg)
+
+
+
+
+
+
+
+
+Help on method changeArg in module broadbean.element:
+
+changeArg(channel: 'str | int', name: 'str', arg: 'str | int', value: 'int | float', replaceeverywhere: 'bool' = False) -> 'None' method of broadbean.element.Element instance
+    Change the argument of a function of the blueprint on the specified
+    channel.
+
+    Args:
+        channel: The channel where the blueprint sits.
+        name: The name of the segment in which to change an argument
+        arg: Either the position (int) or name (str) of
+            the argument to change
+        value: The new value of the argument
+        replaceeverywhere: If True, the same argument is overwritten
+            in ALL segments where the name matches. E.g. 'gaussian1' will
+            match 'gaussian', 'gaussian2', etc. If False, only the segment
+            with exact name match gets a replacement.
+
+    Raises:
+        ValueError: If the specified channel has no blueprint.
+        ValueError: If the argument can not be matched (either the argument
+            name does not match or the argument number is wrong).
+
+
+
+
+
[15]:
+
+
+
# The sequence can be validated
+seq1.checkConsistency()  # returns True if all is well, raises errors if not
+
+# And the sequence can (if valid) be plotted
+plotter(seq1)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_21_0.png +
+
+
+

Tektronix AWG 5014 output

+

(back to ToC)

+

The sequence object can output a tuple matching the call signature of the QCoDeS Tektronix AWG 5014 driver.

+

For the translation from voltage to AWG unsigned integer format to be correct, the voltage ranges and offsets of the AWG channels must be specified (NB: This will NOT work if the channels on the AWG are in high/low mode).

+

Furthermore, the AWG sequencer options should be specified for each sequence element.

+
+
[16]:
+
+
+
seq1.setChannelAmplitude(1, 10e-3)  # Call signature: channel, amplitude (peak-to-peak)
+seq1.setChannelOffset(1, 0)
+seq1.setChannelAmplitude(3, 10e-3)
+seq1.setChannelOffset(3, 0)
+
+# Here we repeat each element twice and then proceed to the next, wrapping over at the end
+seq1.setSequencingTriggerWait(1, 0)
+seq1.setSequencingNumberOfRepetitions(1, 2)
+seq1.setSequencingEventJumpTarget(1, 0)
+seq1.setSequencingGoto(1, 2)
+#
+seq1.setSequencingTriggerWait(2, 0)
+seq1.setSequencingNumberOfRepetitions(2, 2)
+seq1.setSequencingEventJumpTarget(2, 0)
+seq1.setSequencingGoto(1, 3)
+#
+seq1.setSequencingTriggerWait(3, 0)
+seq1.setSequencingNumberOfRepetitions(3, 2)
+seq1.setSequencingEventJumpTarget(3, 0)
+seq1.setSequencingGoto(3, 1)
+
+# then we may finally get the "package" to give the QCoDeS driver for upload
+package = seq1.outputForAWGFile()
+
+# Note that the sequencing information is included in the plot in a way mimicking the
+# way the display of the Tektronix AWG 5014
+plotter(seq1)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_23_0.png +
+
+
+
[17]:
+
+
+
# The package is a SLICEABLE object
+# By slicing and indexing it, one may retrieve different parts of the sequence
+
+chan1_awg_input = package[0]  # returns a tuple yielding an awg file with channel 1
+chan3_awg_input = package[1]  # returns a tuple yielding an awg file with channel 3
+
+both_chans_awg_input = package[
+    :
+]  # returns a tuple yielding an awg file with both channels
+
+# This may be useful to make one big sequence for one experiment and then uploading part of it to one awg
+# and part of it to another (since physical awg's usually don't have enough channels for a big experiment)
+
+# To see how the channels are counted, look up the channels
+package.channels
+
+
+
+
+
[17]:
+
+
+
+
+[1, 3]
+
+
+
+
[18]:
+
+
+
## Example of uploading the sequence (requires having qcodes installed, see https://github.com/QCoDeS/Qcodes)
+
+# from qcodes.instrument_drivers.tektronix.AWG5014 import Tektronix_AWG5014
+# awg = Tektronix_AWG5014('AWG1', 'TCPIP0::172.20.3.57::inst0::INSTR', timeout=40)
+# awg.make_send_and_load_awg_file(*package[:])
+
+
+
+
+

Delays and filter compensation

+

(back to ToC)

+

In a real experimental setting, the signal transmission line may distort and/or delay the pulse sequence. The Sequence object can perform some compensation for this when making the .awg file.

+
+
[19]:
+
+
+
# To delay channel 1 with respect to the other channels, set its delay
+seq1.setChannelDelay(1, 0)
+seq1.setChannelDelay(3, 123e-9)
+
+# To apply for a high pass filter with a cut-off frequency of 1 MHz on channel 3, we can do
+seq1.setChannelFilterCompensation(3, "HP", order=1, f_cut=1e6)
+# or, equivalently,
+seq1.setChannelFilterCompensation(3, "HP", order=1, tau=1e-6)
+
+# Note that setting the filter compensation may invalidate the sequence in the sense that the specified voltage ranges
+# on the AWG may have become too small. The function outputForAWGFile will warn you if this is the case.
+
+newpackage = seq1.outputForAWGFile()
+
+
+
+
+
[20]:
+
+
+
# For sanity checking, it may be helpful to see how the compensated waveforms look.
+# The plotter function can display the delays and filter compensations
+
+plotter(seq1, apply_filters=True, apply_delays=True)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_28_0.png +
+
+
+
+
+

Sequences varying parameters

+

(back to ToC)

+

The module contains a few wrapper functions to easily generate sequences with some parameter(s) varying throughout the sequence. First two examples where a Base element is provided and varied, then an example where an existing Sequence is repeated.

+
+
[21]:
+
+
+
# Example 1: vary the duration of a square pulse
+
+# First, we make a basic element, in this example containing just a single blueprint
+basebp = bb.BluePrint()
+basebp.insertSegment(0, ramp, (0, 0), dur=0.5)
+basebp.insertSegment(1, ramp, (1, 1), dur=1, name="varyme")
+basebp.insertSegment(2, "waituntil", 5)
+basebp.setSR(100)
+
+baseelem = bb.Element()
+baseelem.addBluePrint(1, basebp)
+
+plotter(baseelem)
+
+# Now we make a 5-step sequence varying the duration of the high level
+# The inputs are lists, since several things can be varied (see Example 2)
+channels = [1]
+names = ["varyme"]
+args = ["duration"]
+iters = [[1, 1.5, 2, 2.5, 3]]
+
+seq1 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)
+plotter(seq1)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_30_0.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_30_1.png +
+
+
+
[22]:
+
+
+
# Example 2: Vary the duration AND the high level
+
+# It is straighforward to vary several things throughout the sequence
+
+# We make the same base element as in Example 1
+basebp = bb.BluePrint()
+basebp.insertSegment(0, ramp, (0, 0), dur=0.5)
+basebp.insertSegment(1, ramp, (1, 1), dur=1, name="varyme")
+basebp.insertSegment(2, "waituntil", 5)
+basebp.setSR(100)
+
+baseelem = bb.Element()
+baseelem.addBluePrint(1, basebp)
+
+# Now we make a 5-step sequence varying the duration AND the high level
+# We thus vary 3 things, a duration, a ramp start, and a ramp stop
+channels = [1, 1, 1]
+names = ["varyme", "varyme", "varyme"]
+args = ["duration", "start", "stop"]
+iters = [[1, 1.5, 2, 2.5, 3], [1, 0.8, 0.7, 0.6, 0.5], [1, 0.8, 0.7, 0.6, 0.5]]
+
+seq2 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)
+plotter(seq2)
+
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_31_0.png +
+
+
+
[23]:
+
+
+
# Example 3: Modify the high level of a square pulse inside a sequence
+
+#
+
+pulsebp = bb.BluePrint()
+pulsebp.insertSegment(0, ramp, (0, 0), dur=5e-4)
+pulsebp.insertSegment(1, ramp, (1, 1), dur=1e-3, name="varyme")
+pulsebp.insertSegment(2, "waituntil", 2e-3)
+pulsebp.setSR(1e6)
+
+sinebp = bb.BluePrint()
+sinebp.insertSegment(0, sine, (0.2e3, 0.5, 0.5, 0), dur=10e-3)
+sinebp.setSR(1e6)
+
+elem1 = bb.Element()
+elem1.addBluePrint(1, pulsebp)
+
+elem2 = bb.Element()
+elem2.addBluePrint(1, sinebp)
+
+baseseq = bb.Sequence()
+baseseq.setSR(1e6)
+baseseq.addElement(1, elem1)
+baseseq.addElement(2, elem2)
+
+baseseq.setSequenceSettings(1, 0, 20, 0, 0)
+baseseq.setSequenceSettings(2, 0, 1, 0, 1)
+
+plotter(baseseq)
+
+# now vary this sequence
+
+poss = [1, 1]
+channels = [1, 1]
+names = ["varyme", "varyme"]
+args = ["start", "stop"]
+iters = [[1, 0.75, 0.5], [1, 0.75, 0.5]]
+
+newseq = bb.repeatAndVarySequence(baseseq, poss, channels, names, args, iters)
+plotter(newseq)
+
+
+
+
+
+
+
+
+/opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:190: UserWarning: Deprecation warning. This function is only compatible with AWG5014 output and will be removed. Please use the specific setSequencingXXX methods.
+  warnings.warn(
+
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_32_1.png +
+
+
+
+
+
+../_images/examples_Pulse_Building_Tutorial_32_2.png +
+
+
+
[ ]:
+
+
+

+
+
+
+
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/Pulse_Building_Tutorial.ipynb b/examples/Pulse_Building_Tutorial.ipynb new file mode 100644 index 000000000..bba9c5d79 --- /dev/null +++ b/examples/Pulse_Building_Tutorial.ipynb @@ -0,0 +1,1223 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Pulsebuilding tutorial\n", + "\n", + "## Table of Contents\n", + "\n", + " * [Lingo](#Lingo)\n", + " * [Blueprints](#Blueprints)\n", + " * [Basic blueprinting](#Basic-blueprinting)\n", + " * [Markers](#Markers)\n", + " * [Modifying blueprints](#Modifying-blueprints)\n", + " * [Special segments](#special-segments)\n", + "\n", + " * [Elements](#elements)\n", + " * [Sequences](#sequences)\n", + " * [Tektronix AWG 5014 output](#Tektronix-AWG-5014-output)\n", + " * [Delays and filter compensation](#Delays-and-filter-compensation)\n", + " * [Sequences varying parameters](#Sequences-varying-parameters)\n", + "\n", + "## Lingo\n", + "\n", + "Let's settle on a vocabulary. At the highest level, we construct **sequences**. These sequences will eventually be uploaded to an AWG, e.g. the Tektronix AWG 5014. Each sequence consists of several **elements** than again consist of a number of **channels**. On each channel reside a **waveform** and two **markers**. The waveform and markers may either be added as numpy arrays or as **blueprint**. A blueprint is a set of instructions for making a waveform and two markers and consists of several **segments**\n", + "\n", + "That is to say, the food chain goes: segment -> blueprint -> element -> sequence.\n", + "\n", + "\n", + "### Segments:\n", + "\n", + "#### Intro to Normal segments:\n", + "\n", + "A normal segment consists of a _unique_ name, a function object, a tuple of arguments to the function, and a **duration**. \n", + "\n", + " * The name: can be provided by the user or omitted. If omitted, the segment will get the name of its function. Since all names must be unique, the blueprint _appends numbers_ to names if they occur more than once. The numbers are appended chronologically throughout the blueprint. See example below. Note that valid input (base) names are strings NOT ending in a number. Thus, 'pi/2pulse' is valid, whereas 'pulsepi/2' is not.\n", + " \n", + " * The function: must be a python function taking at least two arguments; the sample rate and the segment duration. If the function takes other arguments (such as ramp slope, frequency, etc.) sample rate and duration arguments must be the last positional arguments. Keyword arguments are currently not allowed. See example at the very end.\n", + " \n", + " * The arguments: are in a tuple of $n-2$ arguments for a function taking $n$ arguments, i.e. specifying everything but the sample rate and duration.\n", + " \n", + "* The duration is a single number giving the desired duration of the segment in seconds. Some responsibility for making this number sensible with respect to the sample rate rests on the user.\n", + "\n", + "#### Intro to Special segments:\n", + "\n", + "A special segment has a (protected) name and a number of arguments. So far, two special segments exist.\n", + " \n", + " * `waituntil`, args [time (int)]: When put in a blueprint, this function ensures that the _next_ segment starts at the absolute time `time` after the start of the element. It does so by filling any excess time with zeros. It fails if the previous segment will finish after time `time`.\n", + " \n", + " * `makemeanfit`. Not implemented yet. Will make the mean of the blueprint be a specified number. Will (eventually) exist in several versions, e.g. one achieving the goal by adding an offset, another by adding an appropriate DC segment at the end of the blueprint.\n", + "\n", + "### Intro to Blueprints:\n", + "\n", + "Consist of a number of segments. Has an associated sample rate.\n", + "\n", + "### Intro to Elements:\n", + "\n", + "Has a number of blueprints on each channel. Can have an arbitraty amount of integer-indexed channels, but the blueprint on each channel must have the same number of points and the same total duration as all the other blueprints.\n", + "\n", + "### Intro to Sequences:\n", + "\n", + "Have an associated sample rate. \n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:20.791122Z", + "iopub.status.busy": "2025-01-21T06:30:20.790953Z", + "iopub.status.idle": "2025-01-21T06:30:21.165979Z", + "shell.execute_reply": "2025-01-21T06:30:21.165368Z" + } + }, + "outputs": [], + "source": [ + "#\n", + "# IMPORTS\n", + "#\n", + "%matplotlib inline\n", + "import matplotlib as mpl\n", + "import numpy as np\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "mpl.rcParams[\"figure.figsize\"] = (8, 3)\n", + "mpl.rcParams[\"figure.subplot.bottom\"] = 0.15" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Blueprints\n", + "\n", + "## Basic blueprinting\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "In this section we show how to construct basic blueprints. The units of the vertical axis is **volts** and the units of the horizontal axis (the durations) is **seconds**." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:21.168323Z", + "iopub.status.busy": "2025-01-21T06:30:21.167934Z", + "iopub.status.idle": "2025-01-21T06:30:21.173777Z", + "shell.execute_reply": "2025-01-21T06:30:21.173281Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Legend: Name, function, arguments, timesteps, durations\n", + "Segment 1: \"ramp\", PulseAtoms.ramp, (0, 0.001), 3e-06\n", + "----------\n", + "Legend: Name, function, arguments, timesteps, durations\n", + "Segment 1: \"ramp\", PulseAtoms.ramp, (0, 0.001), 3e-06\n", + "Segment 2: \"mysine\", PulseAtoms.sine, (500000.0, 0.001, 0.001, 0), 2e-06\n", + "Segment 3: \"ramp2\", PulseAtoms.ramp, (0.001, 0), 3e-06\n", + "Segment 4: \"myfunc\", PulseAtoms.arb_func, ( at 0x7f75b0a63f60>, {'ampl': 500000000.0}), 2e-06\n", + "----------\n" + ] + } + ], + "source": [ + "# The pulsebuilding module comes with a (small) collection of functions appropriate for being segments.\n", + "ramp = bb.PulseAtoms.ramp # args: start, stop\n", + "sine = bb.PulseAtoms.sine # args: freq, ampl, off, phase\n", + "arb_func = bb.PulseAtoms.arb_func # args provided in a dict\n", + "\n", + "# make a blueprint\n", + "\n", + "# The blueprint takes no arguments\n", + "bp1 = bb.BluePrint() # Do-nothing initialisation\n", + "\n", + "# the blueprint is filled via the insertSegment method\n", + "# Call signature: position in the blueprint, function, args, name, duration\n", + "bp1.insertSegment(0, ramp, (0, 1e-3), name=\"\", dur=3e-6)\n", + "\n", + "# A sample rate can be set (Sa/S). Without a sample rate, we can not plot the blueprint.\n", + "bp1.setSR(1e9)\n", + "\n", + "# The blueprint can be inspected. Note that the segment was auto-named 'ramp'\n", + "bp1.showPrint()\n", + "\n", + "# more segments can be added...\n", + "bp1.insertSegment(1, sine, (5e5, 1e-3, 1e-3, 0), name=\"mysine\", dur=2e-6)\n", + "bp1.insertSegment(2, ramp, (1e-3, 0), name=\"\", dur=3e-6)\n", + "bp1.insertSegment(\n", + " 3, arb_func, (lambda t, ampl: ampl * t * t, {\"ampl\": 5e8}), name=\"myfunc\", dur=2e-6\n", + ")\n", + "\n", + "# ... and reinspected\n", + "bp1.showPrint()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:21.175324Z", + "iopub.status.busy": "2025-01-21T06:30:21.175158Z", + "iopub.status.idle": "2025-01-21T06:30:21.253574Z", + "shell.execute_reply": "2025-01-21T06:30:21.253106Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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w8P4m7/5G3z6059WOToiand33nknfvjDH+KZZ55ROxQi6kA6nQ4PPPAA3vGOdwDYGC0dGBjAxz72MXz84x8HAGQyGfT29uLkyZN417vepUqcHDHdh2q1iqeeegrXXHPN1jm9Xo9rrrkGjz/+uIqREVEnmZqawsDAAMbGxvCe97wH8/PzaodERB1qdnYWq6urktzG4/Hg6quvVjW3YWK6D/F4HI1GA729vZLzvb29WF1dVSkqIuokV199NU6ePImHHnoI9913H2ZnZ/H6178euVxO7dCIqANt5i9ay22Mqt0zERFtuf7667f+f8kll+Dqq69GJBLB97//fdx8880qRkZE1DocMd2HYDAIg8GAtbU1yfm1tTX09fWpFBURdTKv14tjx45henpa7VCIqANt5i9ay22YmO6D2WzG5ZdfjocffnjrnCAIePjhh/HqV79axciIqFPl83nMzMygv79f7VCIqAONjo6ir69Pkttks1n85je/UTW34VT+Pt1666246aabcMUVV+Cqq67CV77yFRQKBbz//e9XOzQi6gAf//jH8fa3vx2RSATLy8u44447YDAY8O53v1vt0IioTeXzecmsy+zsLJ555hn4/X6Ew2F89KMfxec+9zlMTk5idHQUf//3f4+BgYGtnftqYGK6T+985zsRi8Vw++23Y3V1FZdeeikeeughxaJhIqKDWFxcxLvf/W4kEgmEQiG87nWvw69//WuEQiG1QyOiNvXkk0/izW9+89bxrbfeCgC46aabcPLkSXzyk59EoVDAX/7lXyKdTuN1r3sdHnroIVitVrVCZh1TIiIiItIGrjElIiIiIk1gYkpEREREmsDElIiIiIg0gYkpEREREWkCE1MiIiIi0gQmpkRERESkCUxMiYiIiEgTmJgSERERkSYwMSUi2kMikUBPTw/m5uYOdTtvetOb8NGPfrQpMTXLu971LnzpS19SOwwioi3s/EREtIdbb70VuVwO999//6FuJ5lMwmQyweVyNSmywzt16hTe8IY3YHZ2Fh6PR+1wiIg4YkpEtJtisYhvfOMbuPnmmw99W36//8BJqSiKqNfrh45B7qKLLsL4+Di+9a1vNf22iYgOgokpEdEufvzjH8NiseAP/uAPts7993//N3Q6HX7605/isssug81mwx/+4R9ifX0dP/nJT3D8+HG43W78xV/8BYrF4tb3yafyK5UKPvWpT2F4eBgWiwUTExP4xje+IbmPn/zkJ7j88sthsVjwq1/9CpVKBR/5yEfQ09MDq9WK173udXjiiSf2/Bn++Z//GZOTk7Barejt7cUNN9wg+frb3/52fPe7323Cb4uI6PCMagdARKRVjz32GC6//PIdv3bnnXfia1/7Gux2O2688UbceOONsFgs+Pa3v418Po8/+7M/wz333INPfepTO37/+973Pjz++OP46le/ihMnTmB2dhbxeFxyzac//Wn80z/9E8bGxuDz+fDJT34SP/jBD/Av//IviEQiuPvuu3Hddddhenoafr9fcR9PPvkkPvKRj+Bf//Vf8ZrXvAbJZBKPPfaY5JqrrroKd911FyqVCiwWywF/U0REzcHElIhoF9FoFAMDAzt+7XOf+xxe+9rXAgBuvvlm3HbbbZiZmcHY2BgA4IYbbsAvf/nLHRPTl156Cd///vfx85//HNdccw0AbH3fdv/wD/+Aa6+9FgBQKBRw33334eTJk7j++usBAPfffz9+/vOf4xvf+AY+8YlPKL5/fn4eDocDb3vb2+ByuRCJRHDZZZdJrhkYGEC1WsXq6ioikch+fzVEREeCU/lERLsolUqwWq07fu2SSy7Z+n9vby/sdrskuezt7cX6+vqO3/vMM8/AYDDgjW984573f8UVV2z9f2ZmBrVabSsZBgCTyYSrrroKzz///I7ff+211yISiWBsbAzvfe978W//9m+S5QUAYLPZAEBxnohIDUxMiYh2EQwGkUqldvyayWTa+r9Op5Mcb54TBGHH791MBl+Jw+HYZ6Q7c7lcePrpp/Gd73wH/f39uP3223HixAmk0+mta5LJJAAgFAod6r6IiJqBiSkR0S4uu+wynD59uum3e/HFF0MQBDzyyCP7/p7x8XGYzWb8z//8z9a5Wq2GJ554AhdccMGu32c0GnHNNdfg7rvvxnPPPYe5uTn84he/2Pr6qVOnMDQ0hGAweLAfhoioibjGlIhoF9dddx1uu+02pFIp+Hy+pt3uyMgIbrrpJnzgAx/Y2vwUjUaxvr6OG2+8ccfvcTgc+Ku/+it84hOfgN/vRzgcxt13341isbhrOasf/ehHOHPmDN7whjfA5/Phxz/+MQRBwHnnnbd1zWOPPYa3vOUtTfvZiIgOg4kpEdEuLr74YrzqVa/C97//fXzoQx9q6m3fd999+MxnPoO//uu/RiKRQDgcxmc+85k9v+cLX/gCBEHAe9/7XuRyOVxxxRX46U9/umvS7PV68R//8R+48847US6XMTk5ie985zu48MILAQDlchk//OEP8dBDDzX1ZyMiOih2fiIi2sODDz6IT3ziEzh16hT0+s5a/XTffffhgQcewM9+9jO1QyEiAsARUyKiPf3Jn/wJpqamsLS0hOHhYbXDaSqTyYR77rlH7TCIiLZwxJSIiIiINKGz5qWIiIiIqG0xMSUiIiIiTWBiSkRERESawMSUiIiIiDSBiSkRERERaQITUyIiIiLSBCamRERERKQJTEyJiIiISBOYmBIRERGRJvx/o2Sz2N23KekAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# For easy overview, we may plot the blueprint\n", + "plotter(bp1)\n", + "\n", + "# The two bleak lines (red, blue) above the graph represent the channel markers.\n", + "# They are described below." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:21.255113Z", + "iopub.status.busy": "2025-01-21T06:30:21.254940Z", + "iopub.status.idle": "2025-01-21T06:30:21.414159Z", + "shell.execute_reply": "2025-01-21T06:30:21.413685Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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1NTUlKWsw1JB/8HBQdTW8tQHIio99jNEZLVy9zO6rmN+fh6dnZ1QbnM4katnTBVFQXd3+c+qra2t5YBBRJZiP0NEVitnP3O7mdIlTH4iIiIiIkdgYEpEREREjsDAdB3C4TA+/OEPIxwO290UIvIo9jNEZDUn9zOuTn4iIiIiIu/gjCkREREROQIDUyIiIiJyBAamREREROQIDEyJiIiIyBEYmK7DU089hc2bNyMSieDuu+/GD3/4Q7ubREQu9f3vfx9ve9vb0NnZCZ/Ph3/7t38zfF/XdXzoQx9CR0cHotEo7r33Xly+fNmexhKR6zz++OM4evQoampq0Nraire//e24ePGi4TGpVAoPP/wwmpqaUF1djV/8xV/E2NiYTS3OY2C6Rl/84hfx6KOP4sMf/jBOnjyJAwcO4L777sP4+LjdTSMiF0okEjhw4ACeeuop0+8/8cQTePLJJ/HZz34W//d//4dYLIb77rsPqVSqzC0lIjd6/vnn8fDDD+Oll17Ct771LWSzWbz5zW9GIpFYfszv/d7v4T//8z/xz//8z3j++ecxPDyMX/iFX7Cx1SwXtWZ33303jh49ik996lMAAE3T0NPTg/e85z34wz/8Q5tbR0Ru5vP58Nxzz+Htb387gPxsaWdnJ973vvfh93//9wEAc3NzaGtrwzPPPIN3vOMdNraWiNxoYmICra2teP755/H6178ec3NzaGlpwbPPPov7778fAHDhwgXs3r0bL774Il7zmtfY0k7OmK5BJpPBiRMncO+99y5fUxQF9957L1588UUbW0ZEXnTt2jWMjo4a+py6ujrcfffd7HOIqChzc3MAgMbGRgDAiRMnkM1mDf3Mrl270Nvba2s/w8B0DSYnJ6GqKtra2gzX29raMDo6alOriMirlvoV9jlEVAqapuG9730vXve612Hfvn0A8v1MKBRCfX294bF29zMB234yEREREVnu4YcfRn9/P37wgx/Y3ZTb4ozpGjQ3N8Pv90uZamNjY2hvb7epVUTkVUv9CvscItqoRx55BF/5ylfw3e9+F93d3cvX29vbkclkMDs7a3i83f0MA9M1CIVCOHz4ML797W8vX9M0Dd/+9rdxzz332NgyIvKiLVu2oL293dDnxONx/N///R/7HCJaE13X8cgjj+C5557Dd77zHWzZssXw/cOHDyMYDBr6mYsXL2JgYMDWfoZL+Wv06KOP4sEHH8SRI0dw11134ROf+AQSiQTe9a532d00InKhhYUFXLlyZfnra9eu4dSpU2hsbERvby/e+9734iMf+Qi2b9+OLVu24E/+5E/Q2dm5nLlPRLSahx9+GM8++yz+/d//HTU1Ncv7Ruvq6hCNRlFXV4eHHnoIjz76KBobG1FbW4v3vOc9uOeee2zLyAdYLmpdPvWpT+HjH/84RkdHceedd+LJJ5/E3XffbXeziMiFvve97+EnfuInpOsPPvggnnnmGei6jg9/+MP4m7/5G8zOzuLHfuzH8OlPfxo7duywobVE5DY+n8/0+t/+7d/iN37jNwDkC+y/733vwxe+8AWk02ncd999+PSnP23rUj4DUyIiIiJyBO4xJSIiIiJHYGBKRERERI7AwJSIiIiIHIGBKRERERE5AgNTIiIiInIEBqZERERE5AgMTImIiIjIERiYEhEREZEjMDAlIlrF1NQUWltbcf369Q29zhvf+Ea8973vLUmbSuUd73gH/uqv/sruZhARLePJT0REq3j00UcxPz+Pp59+ekOvMz09jWAwiJqamhK1bOP6+/vx+te/HteuXUNdXZ3dzSEi4owpEVEhi4uL+NznPoeHHnpow6/V2NhYdFCq6zpyudyG2yDat28ftm3bhn/4h38o+WsTERWDgSkRUQFf/epXEQ6H8ZrXvGb52ve+9z34fD584xvfwMGDBxGNRvGTP/mTGB8fx9e+9jXs3r0btbW1+NVf/VUsLi4uP09cyk+n0/jABz6Anp4ehMNh3HHHHfjc5z5n+Blf+9rXcPjwYYTDYfzgBz9AOp3G7/7u76K1tRWRSAQ/9mM/hmPHjq36Hj796U9j+/btiEQiaGtrw/3332/4/tve9jb80z/9Uwk+LSKijQvY3QAiIqd64YUXcPjwYdPv/emf/ik+9alPoaqqCg888AAeeOABhMNhPPvss1hYWMDP//zP45Of/CQ+8IEPmD7/13/91/Hiiy/iySefxIEDB3Dt2jVMTk4aHvOHf/iH+Mu//Ets3boVDQ0N+IM/+AN8+ctfxt/93d9h06ZNeOKJJ3DffffhypUraGxslH7G8ePH8bu/+7v4+7//e7z2ta/F9PQ0XnjhBcNj7rrrLnz0ox9FOp1GOBwu8pMiIioNBqZERAXcuHEDnZ2dpt/7yEc+gte97nUAgIceegiPPfYYrl69iq1btwIA7r//fnz3u981DUwvXbqEL33pS/jWt76Fe++9FwCWn7fS//t//w9vetObAACJRAKf+cxn8Mwzz+Atb3kLAODpp5/Gt771LXzuc5/D+9//fun5AwMDiMVi+Nmf/VnU1NRg06ZNOHjwoOExnZ2dyGQyGB0dxaZNm9b60RARWYJL+UREBSSTSUQiEdPv7d+/f/n/29raUFVVZQgu29raMD4+bvrcU6dOwe/34w1veMOqP//IkSPL/3/16lVks9nlYBgAgsEg7rrrLpw/f970+W9605uwadMmbN26Fe985zvxj//4j4btBQAQjUYBQLpORGQHBqZERAU0NzdjZmbG9HvBYHD5/30+n+HrpWuappk+dykYvJ1YLLbGlpqrqanByZMn8YUvfAEdHR340Ic+hAMHDmB2dnb5MdPT0wCAlpaWDf0sIqJSYGBKRFTAwYMHce7cuZK/bl9fHzRNw/PPP7/m52zbtg2hUAj/8z//s3wtm83i2LFj2LNnT8HnBQIB3HvvvXjiiSdw+vRpXL9+Hd/5zneWv9/f34/u7m40NzcX92aIiEqIe0yJiAq477778Nhjj2FmZgYNDQ0le93NmzfjwQcfxG/+5m8uJz/duHED4+PjeOCBB0yfE4vF8Nu//dt4//vfj8bGRvT29uKJJ57A4uJiwXJWX/nKV/DKK6/g9a9/PRoaGvDVr34VmqZh586dy4954YUX8OY3v7lk742IaCMYmBIRFdDX14dDhw7hS1/6Et797neX9LU/85nP4IMf/CB+53d+B1NTU+jt7cUHP/jBVZ/zsY99DJqm4Z3vfCfm5+dx5MgRfOMb3ygYNNfX1+Nf//Vf8ad/+qdIpVLYvn07vvCFL2Dv3r0AgFQqhX/7t3/D17/+9ZK+NyKiYvHkJyKiVfzXf/0X3v/+96O/vx+K4q3dT5/5zGfw3HPP4Zvf/KbdTSEiAsAZUyKiVf3Mz/wMLl++jKGhIfT09NjdnJIKBoP45Cc/aXcziIiWccaUiIiIiBzBW+tSRERERORaDEyJiIiIyBEYmBIRERGRIzAwJSIiIiJHYGBKRERERI7AwJSIiIiIHIGBKRERERE5AgNTIiIiInIEBqZERERE5Aj/Hz9KIEVPhgbcAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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1NTUSH/jT3/6Ez70oQ/hP/7jP/DKV74S4+PjeOqppwyvOXToEL74xS8ilUrB7/fnuaeIiAqDgSkR0RJ6enrQ3Nyc87kvfOELeNWrXgUAeO9734v77rsPly5dwqZNmwAAt99+O5544omcgen58+fxox/9CL/5zW9w0003AcDC+xb753/+Z9x8880AgNnZWTz00EN49NFHcdtttwEAHnnkEfzmN7/Bt771Ldx7773S+3t7exEOh/GmN70JkUgEHR0dOHDggOE1zc3NSKfTGBoaQkdHx2p3DRFRUXAon4hoCYlEAoFAIOdz+/btW/jvhoYGhEIhQ3DZ0NCAkZGRnO89fvw43G43Xvva1y7796+//vqF/7506RIymcxCMAwAXq8Xhw4dwtmzZ3O+/+abb0ZHRwc2bdqEd77znfjP//xPQ3oBAASDQQCQthMRmYGBKRHREmprazExMZHzOa/Xu/DfiqIYHs9v0zQt53vng8GVhMPhVbY0t0gkgueeew7f//730dTUhM985jPo6urC5OTkwmvGx8cBAHV1dev6W0REhcDAlIhoCQcOHMCZM2cK/rl79+6Fpml48sknV/2ezZs3w+fz4Y9//OPCtkwmg2effRa7du1a8n0ejwc33XQT7r//fpw8eRLd3d347W9/u/D8qVOn0Nraitra2vz+Z4iICog5pkRES7jllltw3333YWJiAtXV1QX73M7OTtx55514z3veszD5qaenByMjI7jjjjtyviccDuMDH/gA7r33XtTU1KC9vR33338/4vH4kuWsfvazn+Gll17Ca17zGlRXV+MXv/gFNE3D9u3bF17z1FNP4Q1veEPB/t+IiNaDgSkR0RL27t2L6667Dj/60Y/w/ve/v6Cf/dBDD+GTn/wk/vEf/xFjY2Nob2/HJz/5yWXf8+UvfxmapuGd73wnZmZmcP311+NXv/rVkkFzVVUV/vu//xuf+9znkEwmsXXrVnz/+9/H7t27AQDJZBI//elP8dhjjxX0/42IKF9c+YmIaBk///nPce+99+LUqVNwuZyV/fTQQw/hJz/5CX7961+b3RQiIgDsMSUiWtZf/uVf4sKFC+jv70dbW5vZzSkor9eLBx54wOxmEBEtYI8pEREREVmCs8aliIiIiMi2GJgSERERkSUwMCUiIiIiS2BgSkRERESWwMCUiIiIiCyBgSkRERERWQIDUyIiIiKyBAamRERERGQJDEyJiIiIyBL+P1QSAyZxtb0QAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Blueprints may be added together\n", + "bp2 = bp1 + bp1\n", + "plotter(bp2)\n", + "\n", + "# Segments may be removed from a blueprint. They are removed by name.\n", + "bp2.removeSegment(\"ramp2\")\n", + "plotter(bp2)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:21.415953Z", + "iopub.status.busy": "2025-01-21T06:30:21.415778Z", + "iopub.status.idle": "2025-01-21T06:30:21.419009Z", + "shell.execute_reply": "2025-01-21T06:30:21.418562Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Number of points in blueprint: 10000\n", + "Length of blueprint in seconds: 9.999999999999999e-06\n", + "Number of segments in blueprint: 4\n" + ] + } + ], + "source": [ + "# A blueprint has a handful of different lengths one may check\n", + "print(f\"Number of points in blueprint: {bp1.points}\")\n", + "print(f\"Length of blueprint in seconds: {bp1.duration}\")\n", + "print(f\"Number of segments in blueprint: {bp1.length_segments}\")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Markers\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "All markers are OFF by default. Markers can be added to a blueprint (switched ON) in two different ways. Either a marker is specified by its ON time in *absolute time* or by its ON time *relative* to a certain segment." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:21.420552Z", + "iopub.status.busy": "2025-01-21T06:30:21.420380Z", + "iopub.status.idle": "2025-01-21T06:30:21.484964Z", + "shell.execute_reply": "2025-01-21T06:30:21.484434Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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bvLMM8/gwQcfxCOPPIL/+7//wzXXXIPbb78dq6urQ8f/9Kc/xSc/+Uncc889+OUvf4k777wTd955J44cOTLmyImAl156Cffddx9+9rOf4YUXXkCr1cJtt92GarW65f2cTidSqVTv39mzZ8cUMZHclVdeKXst/vjHPx45ludfUpNXX31V9tp94YUXAAC///u/P/I+PPcOx3JRm9x000248cYb8a//+q8AAFEUEY1G8Wd/9mf43Oc+NzD+E5/4BKrVKp577rnesZtvvhnXXnstHn/88bHFTTRMJpNBMBjESy+9hA9+8INDxxw+fBgPPPAACoXCeIMj6vP5z38ezz77LF5//fXzGs/zL6nZAw88gOeeew7Hjx+HRqMZ+DrPvaPxium6ZrOJ1157DbfeemvvmFarxa233opXXnll6H1eeeUV2XgAuP3220eOJxqnYrEIAPB6vVuOq1Qq2LdvH6LRKH73d38Xb7755jjCIxpw/PhxRCIRXHLJJbjrrrsQj8dHjuX5l9Sq2WziySefxN133z00Kd3Ac+9wTEzXZbNZdDodhEIh2fFQKISVlZWh91lZWdnReKJxEUURDzzwAN7//vfjqquuGjnu8ssvxze+8Q18+9vfxpNPPglRFPG+970PS0tLY4yWqDtjdfjwYTz//PN47LHHcPr0aXzgAx9AuVweOp7nX1KrZ599FoVCAX/0R380cgzPvaPplQ6AiHbffffdhyNHjmy5Rg8AbrnlFtxyyy292+973/tw6NAhfPWrX8UXv/jFvQ6TqOeOO+7o/f/d7343brrpJuzbtw/f/OY3cc899ygYGdHOfP3rX8cdd9yBSCQycgzPvaMxMV3n9/uh0+mQTqdlx9PpNObm5obeZ25ubkfjicbh/vvvx3PPPYeXX34ZCwsLO7qvwWDAddddhxMnTuxRdETnx+1247LLLhv5WuT5l9To7Nmz+P73v4///u//3tH9eO49h1P564xGI2644Qa8+OKLvWOiKOLFF1+UfarZ7JZbbpGNB4AXXnhh5HiivSRJEu6//35861vfwg9+8APs379/x4/R6XTwxhtvIBwO70GEROevUqng5MmTI1+LPP+SGj3xxBMIBoP46Ec/uqP78dy7iUQ9Tz/9tGQymaTDhw9LR48elf7kT/5Ecrvd0srKiiRJkvSHf/iH0uc+97ne+J/85CeSXq+X/umf/kl66623pEceeUQyGAzSG2+8odS3QDPsM5/5jORyuaQf/vCHUiqV6v2r1Wq9Mf2v4S984QvS9773PenkyZPSa6+9Jv3BH/yBZDabpTfffFOJb4Fm2F/+5V9KP/zhD6XTp09LP/nJT6Rbb71V8vv90urqqiRJPP+S+nU6HSkWi0l/8zd/M/A1nnvPH6fyN/nEJz6BTCaDhx9+GCsrK7j22mvx/PPP9xbYx+NxaLXnLjK/733vw1NPPYW/+7u/w9/+7d/i4MGDePbZZ7fcbEK0Vx577DEAwIc+9CHZ8SeeeKK3CL//NSwIAu69916srKzA4/HghhtuwE9/+lO8613vGlfYRACApaUlfPKTn0Qul0MgEMCv/dqv4Wc/+xkCgQAAnn9J/b7//e8jHo/j7rvvHvgaz73nj3VMiYiIiEgVuMaUiIiIiFSBiSkRERERqQITUyIiIiJSBSamRERERKQKTEyJiIiISBWYmBIRERGRKjAxJSIiIiJVYGJKRERERKrAxJSISAG5XA7BYBBnzpzZclw2m0UwGMTS0tJ4AiMiUhA7PxERKeDBBx9EuVzG1772tW3Hfvazn4UgCPj6178+hsiIiJTDxJSIaMxqtRrC4TC+973v4eabb952/JtvvokbbrgByWQSXq93DBESESmDU/lERGP23e9+FyaTqZeUCoKAu+66C4FAABaLBQcPHsQTTzzRG3/llVciEongW9/6llIhExGNhV7pAIiIZs2PfvQj3HDDDb3bf//3f4+jR4/if/7nf+D3+3HixAnU63XZfd773vfiRz/6Ee65555xh0tENDZMTImIxuzs2bOIRCK92/F4HNdddx3e8573AAAWFxcH7hOJRPDLX/5yXCESESmCU/lERGNWr9dhNpt7tz/zmc/g6aefxrXXXou//uu/xk9/+tOB+1gsFtRqtXGGSUQ0dkxMiYjGzO/3QxCE3u077rgDZ8+exV/8xV8gmUziIx/5CD772c/K7pPP5xEIBMYdKhHRWDExJSIas+uuuw5Hjx6VHQsEAvj0pz+NJ598El/+8pfxb//2b7KvHzlyBNddd904wyQiGjsmpkREY3b77bfjzTff7F01ffjhh/Htb38bJ06cwJtvvonnnnsOhw4d6o2v1Wp47bXXcNtttykVMhHRWDAxJSIas6uvvhrXX389vvnNbwIAjEYjHnroIbz73e/GBz/4Qeh0Ojz99NO98d/+9rcRi8XwgQ98QKmQiYjGggX2iYgU8J3vfAd/9Vd/hSNHjkCr3foawc0334w///M/x6c+9akxRUdEpAyWiyIiUsBHP/pRHD9+HMvLy4hGoyPHZbNZfOxjH8MnP/nJMUZHRKQMXjElIiIiIlXgGlMiIiIiUgUmpkRERESkCkxMiYiIiEgVmJgSERERkSowMSUiIiIiVWBiSkRERESqwMSUiIiIiFSBiSkRERERqQITUyIiIiJShf8Ps7vhZ9l8vZEAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Absolute time marker specification\n", + "\n", + "# The blueprint has a list of tuples for each marker. The tuples are (switch_on_time, duration)\n", + "\n", + "# create a blueprint\n", + "bp_atm = bb.BluePrint()\n", + "bp_atm.setSR(100)\n", + "bp_atm.insertSegment(0, ramp, (0, 1), dur=3)\n", + "bp_atm.insertSegment(1, sine, (0.5, 1, 1, 0), dur=2)\n", + "bp_atm.insertSegment(2, ramp, (1, 0), dur=3)\n", + "\n", + "# specify markers in absolute time\n", + "bp_atm.marker1 = [(1, 0.5), (2, 0.5)]\n", + "bp_atm.marker2 = [(1.5, 0.2), (2.5, 0.1)]\n", + "\n", + "plotter(bp_atm)" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:21.486577Z", + "iopub.status.busy": "2025-01-21T06:30:21.486409Z", + "iopub.status.idle": "2025-01-21T06:30:21.608226Z", + "shell.execute_reply": "2025-01-21T06:30:21.607774Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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D408Ip/P48iRI/j85z9v9VDIo37wgx/gsccew49//GN873vfQ7VaxYMPPoh8Pm/10MgjRkZG8Hd/93c4efIkXn31Vdx///34tV/7NZw+fdrqoVGLsMVWG9x9992488478bnPfQ4AoGkaRkdH8Xu/93v41Kc+ZfHoyGskScLzzz+PD3zgA1YPhTxscXERAwMD+MEPfoB3vvOdVg+HPKqnpwef+cxn8Oijj1o9FGoBzsS2WKVSwcmTJ/HAAw+sH5NlGQ888ABeeuklC0dGRGSddDoNYC1EEHWaqqp47rnnkM/nce+991o9HGoRn9UDcJulpSWoqorBwUHD8cHBQZw5c8aiURERWUfTNPzBH/wB7rvvPtxyyy1WD4c85LXXXsO9996LUqmEWCyG559/HjfddJPVw6IWYYglIqK2euyxx/D666/jhz/8odVDIY85fPgwTp06hXQ6ja9//et4+OGH8YMf/IBB1iUYYlusr68PiqJgfn7ecHx+fh67du2yaFRERNb4xCc+gW9961s4ceIERkZGrB4OeUwgEMCBAwcAAMeOHcMrr7yCf/zHf8QXv/hFi0dGrcCa2BYLBAI4duwYXnjhhfVjmqbhhRdeYB0OEXmGruv4xCc+geeffx7//d//jfHxcauHRARN01Aul60eBrUIZ2Lb4Pjx43j44Ydxxx134K677sJTTz2FfD6PRx55xOqhkUfkcjmcP39+/fHExAROnTqFnp4e7Nmzx8KRkVc89thjePbZZ/HNb34T8Xgcc3NzAIBkMolwOGzx6MgLHn/8cTz00EPYs2cPstksnn32WXz/+9/Hd7/7XauHRi3CFltt8rnPfQ6f+cxnMDc3h9tuuw3/9E//hLvvvtvqYZFHfP/738cv//Ivm44//PDDeOaZZzo/IPIcSZIaHn/66afx0Y9+tLODIU969NFH8cILL+DKlStIJpO49dZb8ad/+qd4z3veY/XQqEUYYomIiIjIcVgTS0RERESOwxBLRERERI7DEEtEREREjsMQS0RERESOwxBLRERERI7DEEtEREREjsMQS0RERESOwxBLRERERI7DEEtE5ADLy8sYGBjA5OTkNZ+3tLSEgYEBXL58uTMDIyKyCHfsIiJygOPHjyObzeJLX/rSdZ/7yU9+EqlUCl/+8pc7MDIiImswxBIR2VyhUMDu3bvx3e9+F/fcc891n3/69GkcO3YMs7Oz6Onp6cAIiYg6j+UEREQ291//9V8IBoPrATaVSuEjH/kI+vv7EQ6HcfDgQTz99NPrz7/55psxNDSE559/3qohExG1nc/qARAR0bW9+OKLOHbs2PrjP//zP8cbb7yBb3/72+jr68P58+dRLBYNX3PXXXfhxRdfxKOPPtrp4RIRdQRDLBGRzV26dAlDQ0Prj6empnD06FHccccdAICxsTHT1wwNDeFnP/tZp4ZIRNRxLCcgIrK5YrGIUCi0/vh3f/d38dxzz+G2227Dn/zJn+BHP/qR6WvC4TAKhUInh0lE1FEMsURENtfX14dUKrX++KGHHsKlS5fwh3/4h5idncW73/1ufPKTnzR8zcrKCvr7+zs9VCKijmGIJSKyuaNHj+KNN94wHOvv78fDDz+Mr371q3jqqafwL//yL4a/f/3113H06NFODpOIqKMYYomIbO69730vTp8+vT4b+8QTT+Cb3/wmzp8/j9OnT+Nb3/oWbrzxxvXnFwoFnDx5Eg8++KBVQyYiajuGWCIim3vb296G22+/Hf/+7/8OAAgEAnj88cdx66234p3vfCcURcFzzz23/vxvfvOb2LNnD97xjndYNWQiorbjZgdERA7wn//5n/jjP/5jvP7665Dla88/3HPPPfj93/99fPjDH+7Q6IiIOo8ttoiIHOB973sfzp07h5mZGYyOjjZ93tLSEj74wQ/it37rtzo4OiKizuNMLBERERE5DmtiiYiIiMhxGGKJiIiIyHEYYomIiIjIcRhiiYiIiMhxGGKJiIiIyHEYYomIiIjIcRhiiYiIiMhxGGKJiIiIyHEYYomIiIjIcf5/Hc8DC6lFJpIAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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RsLE6suvxurLr4bruktinqSm61h16RVYdekVUQ8lVD39A+js7cdEef6dvIhEkYbYvr4+9PW13zwfIiJqnaUQYImocZGG2EacPHkSY2NjOHnyJGzbxoEDBwAAl19+OTo7+dcbERER0VISmxD78MMP45//+Z9nbm/fvh0A8N///d+44YYbIhoVEREREUUhNt0Jnn76abiuK/3HAEtERES09MQmxBIRERER1cRmOkEruK4LANz0YAmzbRu5fA7A9PeBwVYuyuBnoyZ+LhQGfp+pK4rPppbTarltLksqxE5NTQEANz0gIiIiUtzU1BR6enrmvF9zF4q5bcRxHJw5cwZdXV2htGyZnJzE4OAghoaG0N0t7z5DRF78mSFqDH9mqB25roupqSkMDAxA1+ee+bqkKrG6rmPt2rn35A5Kd3c3/3EhagB/Zogaw58ZajfzVWBruLCLiIiIiGKHIZaIiIiIYochNkCpVArf/OY3kUqloh4KUSzwZ4aoMfyZoaVsSS3sIiIiIqL2wEosEREREcUOQywRERERxQ5DLBERERHFDkMsEREREcUOQ2xAvv/972PDhg1Ip9PYs2cPXnvttaiHRKSsxx57DLt27UJXVxdWrVqF22+/He+++27UwyKKhb/8y7+Epmn46le/GvVQiELFEBuA5557Dnv37sU3v/lN7N+/H1u3bsUtt9yC8+fPRz00IiX9/Oc/x/33349XX30VP/3pT2FZFm6++Wbk8/moh0aktNdffx0/+MEPsGXLlqiHQhQ6ttgKwJ49e7Br1y5873vfAwA4joPBwUH84R/+IR588MGIR0ekvpGREaxatQo///nP8clPfjLq4RApKZfLYceOHfi7v/s7PPLII9i2bRsef/zxqIdFFBpWYlusUqlg3759uOmmm2aO6bqOm266Cb/85S8jHBlRfExMTAAAVqxYEfFIiNR1//3347bbbvP8viFaShJRD6DdjI6OwrZt9Pf3e4739/fjyJEjEY2KKD4cx8FXv/pVfOITn8A111wT9XCIlPTss89i//79eP3116MeClFkGGKJSCn3338/Dh48iF/84hdRD4VISUNDQ/ijP/oj/PSnP0U6nY56OESRYYhtsd7eXhiGgXPnznmOnzt3DqtXr45oVETx8MADD+AnP/kJXn75Zaxduzbq4RApad++fTh//jx27Ngxc8y2bbz88sv43ve+h3K5DMMwIhwhUTg4J7bFkskkdu7ciZdeemnmmOM4eOmll/Dxj388wpERqct1XTzwwAP48Y9/jJ/97Ge49NJLox4SkbJuvPFGvP322zhw4MDMf9dddx3uvPNOHDhwgAGWlgxWYgOwd+9e3HXXXbjuuuuwe/duPP7448jn87jnnnuiHhqRku6//34888wzeP7559HV1YXh4WEAQE9PDzKZTMSjI1JLV1eXNF+8o6MDK1eu5DxyWlIYYgPw2c9+FiMjI3j44YcxPDyMbdu24cUXX5QWexHRtL//+78HANxwww2e40899RTuvvvu8AdERETKY59YIiIiIoodzoklIiIiothhiCUiIiKi2GGIJSIiIqLYYYglIiIiothhiCUiIiKi2GGIJSIiIqLYYYglIiIiothhiCUiIiKi2GGIJSKKgQsXLmDVqlU4fvz4vI8bHR3FqlWrcOrUqXAGRkQUEe7YRUQUA3v37sXU1BT+8R//ccHHfv3rX8f4+Dj+6Z/+KYSRERFFgyGWiEhxhUIBl1xyCf7rv/4L119//YKPP3ToEHbu3IkzZ85gxYoVIYyQiCh8nE5ARKS4//iP/0AqlZoJsOPj47jzzjvR19eHTCaDTZs24amnnpp5/ObNmzEwMIAf//jHUQ2ZiChwiagHQERE83vllVewc+fOmdt/9md/hsOHD+M///M/0dvbi/fffx/FYtHznN27d+OVV17BF77whbCHS0QUCoZYIiLFnThxAgMDAzO3T548ie3bt+O6664DAGzYsEF6zsDAAN58882whkhEFDpOJyAiUlyxWEQ6nZ65/eUvfxnPPvsstm3bhj/90z/F//zP/0jPyWQyKBQKYQ6TiChUDLFERIrr7e3F+Pj4zO1bb70VJ06cwNe+9jWcOXMGN954I77+9a97njM2Noa+vr6wh0pEFBqGWCIixW3fvh2HDx/2HOvr68Ndd92FH/7wh3j88cfxD//wD577Dx48iO3bt4c5TCKiUDHEEhEp7pZbbsGhQ4dmqrEPP/wwnn/+ebz//vs4dOgQfvKTn+Cqq66aeXyhUMC+fftw8803RzVkIqLAMcQSESnu2muvxY4dO/CjH/0IAJBMJvHQQw9hy5Yt+OQnPwnDMPDss8/OPP7555/HunXr8Ju/+ZtRDZmIKHDc7ICIKAZeeOEF/Mmf/AkOHjwIXZ+//nD99dfjK1/5Cj7/+c+HNDoiovCxxRYRUQzcdtttOHr0KE6fPo3BwcE5Hzc6OorPfOYz+NznPhfi6IiIwsdKLBERERHFDufEEhEREVHsMMQSERERUewwxBIRERFR7DDEEhEREVHsMMQSERERUewwxBIRERFR7DDEEhEREVHsMMQSERERUewwxBIRERFR7Px/PW3lULPXKZEAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Relative time marker specification\n", + "\n", + "bp_rtm = bb.BluePrint()\n", + "bp_rtm.setSR(100)\n", + "bp_rtm.insertSegment(0, ramp, (0, 1), dur=1)\n", + "bp_rtm.insertSegment(1, ramp, (1, 1), dur=1)\n", + "bp_rtm.insertSegment(\n", + " 2, sine, (1.675, 1, 0, np.pi / 2), dur=1.5, name=\"mysine\"\n", + ") # This is the important segment\n", + "# make marker 1 go ON a bit before the sine comes on\n", + "bp_rtm.setSegmentMarker(\n", + " \"mysine\", (-0.1, 0.2), 1\n", + ") # segment name, (delay, duration), markerID\n", + "# make marker 2 go ON halfway through the sine\n", + "bp_rtm.setSegmentMarker(\"mysine\", (0.75, 0.1), 2)\n", + "\n", + "plotter(bp_rtm)\n", + "\n", + "# Even if we insert segments before and after the sine, the markers \"stick\" to the sine segment\n", + "bp_rtm.insertSegment(0, ramp, (0, 0), dur=1)\n", + "bp_rtm.insertSegment(-1, ramp, (0, 0.2), dur=1)\n", + "\n", + "plotter(bp_rtm)\n", + "\n", + "\n", + "# NB: the two different ways of inputting markers will never directly conflict, since one only specifies when to turn\n", + "# markers ON. It is up to the user to ensure that markers switch off again as expected, i.e. that different marker\n", + "# specifications do not overlap." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Modifying blueprints\n", + "([back to ToC](#Table-of-Contents))" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:21.609881Z", + "iopub.status.busy": "2025-01-21T06:30:21.609704Z", + "iopub.status.idle": "2025-01-21T06:30:21.795712Z", + "shell.execute_reply": "2025-01-21T06:30:21.795185Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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v/M7cfNHHnkEL7/8cvIDESWQTqfbcf7SSy/hD//wD5MbhijBHnvsMZw5cwYzMzMoKChAW1sb/vzP/xwf//jHRUfbwjJLRERERGmL18wSERERUdpimSUiIiKitMUyS0RERERpi2WWiIiIiNIWyywRERERpS2WWSIiIiJKWyyzRERERJS2WGaJiIiIKG2xzBIRpZHFxUWUl5djYmLilo9bWFhAeXk5JicnkxOMiEgQ7gBGRJRGTp48idXVVfzgBz+47WO/+tWvwufz4V/+5V+SkIyISAyWWSKiNLG2toaqqir87Gc/w3333Xfbx4+OjqKjowPT09MoLi5OQkIiouTjZQZERGniJz/5CUwm01aR9fl8+MIXvoCysjJYLBbU19fjpZde2np8c3MzbDYb3njjDVGRiYgSTi86ABER3Zlf/OIX6Ojo2Pr1X//1X2NsbAw//elPUVpaiuvXryMUCmme093djV/84hd47LHHkh2XiCgpWGaJiNKEy+WCzWbb+rXb7UZ7ezs6OzsBAHV1dXHPsdlsuHDhQrIiEhElHS8zICJKE6FQCGazeevXf/RHf4TXXnsNR44cwde//nX86le/inuOxWLB2tpaMmMSESUVyywRUZooLS2Fz+fb+vUDDzwAl8uFr3zlK5iensbHPvYxfPWrX9U8Z2lpCWVlZcmOSkSUNCyzRERpor29HWNjY5pZWVkZHnnkEbzyyit49tln8f3vf19zfGRkBO3t7cmMSUSUVCyzRERp4sSJExgdHd06O/vUU0/hrbfewvXr1zE6Ooof/ehHaGxs3Hr82toaBgYG8IlPfEJUZCKihGOZJSJKE62trTh69Ch++MMfAgCMRiNOnTqFtrY2fPjDH4Ysy3jttde2Hv/WW2/B4XDgt3/7t0VFJiJKOG6aQESURn784x/ja1/7GkZGRiBJtz4fcd999+FP/uRP8Ad/8AdJSkdElHxcmouIKI08+OCDuHbtGqampmC323d93MLCAj796U/j85//fBLTERElH8/MEhEREVHa4jWzRERERJS2WGaJiIiIKG2xzBIRERFR2mKZJSIiIqK0xTJLRERERGmLZZaIiIiI0hbLLBERERGlLZZZIiIiIkpbLLNERERElLb+P/nXN65/l430AAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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167V448/ro8++mglsiLFDfV2aain0zJLd7lVUdfAwhcApLjM3PxFF8JmQtOGUmE1LKkBPP/886qurtbrr7+u3bt365133lFzc7POnj2rw4cP65lnntHMzIzuuecefelLX1J7e/tK5UaKGQ8OKXC+1TKzfXLWYNq8swYBAKkpv7xKeeVVlllockK+1uMxexZIHmlLufFHH32k9957T5s3b17w92+//XZ97Wtf08svv6zXX39d77//vmpqapYlKFJXaGpSvpbYha/yms0xnwIDAEhtpbfUampsRBPBK3Oz8SsDunzxrEpv4QzyZLSkMvvv//7v13U7p9OpJ5544oYCAZ8WiYTla23WzPSUZV5YtU65JV5DqQAA8cpun33X7uLRDyzfOwZ9HXJle5RbyveOZLPkCw0feOABHThwgPPbsCr851otP11LUlZ+kYqrWfgCACwsLcOpyvqmmH2K3vZTmhgZNpQKK2XJZXZoaEj33Xef1qxZo3379unChQsrkQvQUE+nhv3dllm6O1MVtQ2yzfsIQwAAPs3tyVPZhnrLLBqJzC6EzXu3D4ltyWX20KFDunDhgh577DG98cYbqqmp0V133aU333xTU1M8OLA8xocHFbjQZpldXfhypKcbSgUASCR5ZZXK966xzGamJhf84B0krhs6z2jt2rX6q7/6K124cEEHDx6U1+vV448/rvLycj355JM6cuTIcudECglNTqh7gYUv76atcmXlGEoFAEhEpetrlZlbYJlNDA/FvGCCxHXTh3PeddddeuONN+T3+7V//3699dZb2rlz53JkQwqKfHImYHjemYCFa26Rp6jMUCoAQKKy2e2qqGtQmtNlmQ/1dOrKvEvZkJiW5aT5ixcv6u///u/17LPPanh4WLt3716OPxYpyH/udMyntWQXFKt47QZDiQAAiS4tw6mqzdtjFsL851piloyReG64zE5OTuqNN97QXXfdpZqaGv3rv/6rHnvsMV28eFEHDhxYzoxIEYO+Dg0HeiyzDHeWvLXbWPgCANwUV7ZH5TVbLDMWwpLDks6ZlaQPP/xQr732mt5++21NTk7qD/7gD3TgwAHdfffdFA7csLGhAQUunLHM7I40VW5ukiONhS8AwM3LLfVqcjSoQV/H3GxmekrdLce0Ztttstsd5sLhhi25zN5xxx1qaGjQ97//fT300EPKz89fiVxI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fvnLX8bMvvKVr+grX/nKCqcCAABAvEv60wwAAACQvOLildnVFP3k07hW68MTgKUIh8MaHRuVNPsYdTgchhMBSDY8z2CpTDxmrva0aPTaH5+bcmV2ZGREklbtwxMAAABwY0ZGRpSbm/uZt7FFr6fyJpFIJKKenh7l5OSsygcYBINBVVVVqaurSx6PZ8X/PgCph+cZACtttZ9notGoRkZG5PV6LQcBLCTlXpm12+2qrKxc9b/X4/HwTQbAiuJ5BsBKW83nmWu9InsVC2AAAABIWJRZAAAAJCzK7ApzOp165pln5HQ6TUcBkKR4ngGw0uL5eSblFsAAAACQPHhlFgAAAAmLMgsAAICERZkFAABAwqLMAgAAIGFRZlfYSy+9pOrqarlcLu3cuVMffvih6UgAksT+/ft12223KScnRyUlJbr//vt15swZ07EAJKm//du/lc1m07e+9S3TUSwosyvo7bff1t69e/XMM8/o6NGjamho0J49e3T58mXT0QAkgV/96ld68skn9Zvf/EYHDx5UKBTSPffco7GxMdPRACSZjz76SK+88oq2bdtmOkoMjuZaQTt37tRtt92mF198UZIUiURUVVWlP/mTP9Gf/dmfGU4HINn09fWppKREv/rVr/SFL3zBdBwASWJ0dFTbt2/XD37wA/3N3/yNGhsb9cILL5iONYdXZlfI9PS0jhw5ot27d8/N7Ha7du/ercOHDxtMBiBZDQ8PS5IKCgoMJwGQTJ588kndd999lk4TT9JMB0hW/f39CofDKi0ttcxLS0vV1tZmKBWAZBWJRPStb31Ln//857VlyxbTcQAkibfeektHjx7VRx99ZDrKoiizAJAEnnzySZ06dUq//vWvTUcBkCS6urr0zW9+UwcPHpTL5TIdZ1GU2RVSVFQkh8OhQCBgmQcCAZWVlRlKBSAZPfXUU/rZz36m9957T5WVlabjAEgSR44c0eXLl7V9+/a5WTgc1nvvvacXX3xRU1NTcjgcBhPO4prZFZKRkaEdO3bo0KFDc7NIJKJDhw5p165dBpMBSBbRaFRPPfWUfvrTn+r//u//tG7dOtORACSRu+++WydPnlRzc/Pc16233qqHHnpIzc3NcVFkJV6ZXVF79+7VI488oltvvVW33367XnjhBY2NjenRRx81HQ1AEnjyySf15ptv6t1331VOTo78fr8kKTc3V26323A6AIkuJycn5hr8rKwsFRYWxtW1+ZTZFfTggw+qr69P+/btk9/vV2Njow4cOBCzFAYAN+KHP/yhJOnOO++0zF9//XX98R//8eoHAgADOGcWAAAACYtrZgEAAJCwKLMAAABIWJRZAAAAJCzKLAAAABIWZRYAAAAJizILAACAhEWZBQAAQMKizAIAACBhUWYBIIEMDAyopKREHR0dn3m7/v5+lZSUqLu7e3WCAYAhfAIYACSQvXv3amRkRK+++uo1b/ud73xHQ0ND+ud//udVSAYAZlBmASBBjI+Pq7y8XP/zP/+jO+6445q3P336tHbs2KGenh4VFBSsQkIAWH1cZgAACeIXv/iFnE7nXJEdGhrSQw89pOLiYrndbtXU1Oj111+fu/3mzZvl9Xr105/+1FRkAFhxaaYDAACuz/vvv68dO3bM/fov//Iv1dLSov/+7/9WUVGRzp07p4mJCct9br/9dr3//vt67LHHVjsuAKwKyiwAJIhLly7J6/XO/bqzs1NNTU269dZbJUnV1dUx9/F6vTp27NhqRQSAVcdlBgCQICYmJuRyueZ+/Y1vfENvvfWWGhsb9b3vfU8ffPBBzH3cbrfGx8dXMyYArCrKLAAkiKKiIg0NDc39+t5779WlS5f07W9/Wz09Pbr77rv1ne98x3KfwcFBFRcXr3ZUAFg1lFkASBBNTU1qaWmxzIqLi/XII4/ojTfe0AsvvKAf/ehHlt8/deqUmpqaVjMmAKwqyiwAJIg9e/bo9OnTc6/O7tu3T++++67OnTun06dP62c/+5nq6urmbj8+Pq4jR47onnvuMRUZAFYcZRYAEsTWrVu1fft2/eQnP5EkZWRk6Omnn9a2bdv0hS98QQ6HQ2+99dbc7d99912tWbNGv/M7v2MqMgCsOD40AQASyM9//nN997vf1alTp2S3f/brEXfccYf+9E//VH/4h3+4SukAYPVxNBcAJJD77rtP7e3t8vl8qqqqWvR2/f39+vKXv6yvfvWrq5gOAFYfr8wCAAAgYXHNLAAAABIWZRYAAAAJizILAACAhEWZBQAAQMKizAIAACBhUWYBAACQsCizAAAASFiUWQAAACQsyiwAAAAS1v8Pjv/yf0QC4hQAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# An essential feature of blueprints is that they can be modified\n", + "\n", + "bp_mod = bb.BluePrint()\n", + "bp_mod.setSR(100)\n", + "\n", + "bp_mod.insertSegment(0, ramp, (0, 0), name=\"before\", dur=1)\n", + "bp_mod.insertSegment(1, ramp, (1, 1), name=\"plateau\", dur=1)\n", + "bp_mod.insertSegment(2, ramp, (0, 0), name=\"after\", dur=1)\n", + "\n", + "plotter(bp_mod)\n", + "\n", + "# Functional arguments can be changed\n", + "\n", + "# They are looked up by segment name\n", + "bp_mod.changeArg(\n", + " \"before\", \"stop\", 1\n", + ") # the argument to change may either be the argument name or its position\n", + "bp_mod.changeArg(\"after\", 0, 1)\n", + "\n", + "plotter(bp_mod)\n", + "\n", + "# Durations can also be changed\n", + "bp_mod.changeDuration(\"plateau\", 2)\n", + "\n", + "plotter(bp_mod)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Special segments\n", + "([back to ToC](#Table-of-Contents))" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:21.797468Z", + "iopub.status.busy": "2025-01-21T06:30:21.797276Z", + "iopub.status.idle": "2025-01-21T06:30:21.921677Z", + "shell.execute_reply": "2025-01-21T06:30:21.921174Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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ioqKdNddd+m+++4zXB1gn2PHjmnChAl69dVXdckll5guB7BFe3u75syZo1/+8pf6yU9+ovLycj388MOmywKG1X333ac333xTr7/+uulSTonOrE16e3tVW1urxYsXDxzz+/1avHixNm7caLAywH6tra2SpJycHMOVAPZZvny5li5dGvc6D3jNX//6V82dO1fXXnutJkyYoIqKCq1du9Z0WXEIszZpbm5WNBpVXl5e3PG8vDw1NjYaqgqwn2VZuueee3TxxRdr5syZpssBbPHss89q69atWr16telSAFvt379fv/rVr1RSUqKXX35Zd955p+6++249/fTTpksbEDRdAABvWb58uXbu3Kk33njDdCmALRoaGvStb31LGzZsUDgcNl0OYCvLsjR37lw98MADkqSKigrt3LlTa9as0c0332y4upPozNokNzdXgUBATU1NccebmpqUn59vqCrAXitWrNCLL76of/3rX5o0aZLpcgBb1NbW6ujRo5ozZ46CwaCCwaBeffVVPfLIIwoGg4pGo6ZLBIZNQUGBpk+fHnestLRU9fX1hipKRJi1SXp6uiorK1VTUzNwzLIs1dTUaOHChQYrA4ZfLBbTihUr9Je//EX//Oc/NXXqVNMlAba5/PLLtWPHDm3fvn3ga+7cubrhhhu0fft2BQIB0yUCw+biiy9O2GrxvffeU3FxsaGKEjHNwEbV1dW6+eabNXfuXM2fP18PP/ywOjo6dMstt5guDRhWy5cv1zPPPKMXXnhBmZmZA/PCs7OzlZGRYbg6YHhlZmYmzAcfPXq0xo0bxzxxeM69996rRYsW6YEHHtDXv/51bdq0SU888YSeeOIJ06UNYGsumz366KP6v//7PzU2Nqq8vFyPPPKIqqqqTJcFDCufz5f0+FNPPaVvfvObzhYDGHDppZeyNRc868UXX9TKlSu1b98+TZ06VdXV1Vq2bJnpsgYQZgEAAOBazJkFAACAaxFmAQAA4FqEWQAAALgWYRYAAACuRZgFAACAaxFmAQAA4FqEWQAAALgWYRYAAACuRZgFABc5fvy4JkyYoA8//PC05zU3N2vChAk6ePCgM4UBgCFcAQwAXKS6ulptbW1au3btGc/99re/rZaWFv3mN79xoDIAMIMwCwAu0dnZqYKCAr388stasGDBGc/ftWuXKisrdfjwYeXk5DhQIQA4j2kGAOASf/vb3xQKhQaCbEtLi2644QaNHz9eGRkZKikp0VNPPTVw/owZM1RYWKi//OUvpkoGANsFTRcAADg7r7/+uiorKwdu/+AHP9Du3bv197//Xbm5uXr//ffV1dUVd5/58+fr9ddf12233eZ0uQDgCMIsALhEXV2dCgsLB27X19eroqJCc+fOlSRNmTIl4T6FhYXatm2bUyUCgOOYZgAALtHV1aVwODxw+84779Szzz6r8vJyffe739Vbb72VcJ+MjAx1dnY6WSYAOIowCwAukZubq5aWloHbV155perq6nTvvffq8OHDuvzyy/Xtb3877j4nTpzQ+PHjnS4VABxDmAUAl6ioqNDu3bvjjo0fP14333yz1q1bp4cfflhPPPFE3Pd37typiooKJ8sEAEcRZgHAJZYsWaJdu3YNdGdXrVqlF154Qe+//7527dqlF198UaWlpQPnd3Z2qra2VldccYWpkgHAdoRZAHCJWbNmac6cOfrjH/8oSUpPT9fKlSs1e/ZsXXLJJQoEAnr22WcHzn/hhRc0efJkfe5znzNVMgDYjosmAICLvPTSS/rOd76jnTt3yu8/fT9iwYIFuvvuu/WNb3zDoeoAwHlszQUALrJ06VLt27dPhw4dUlFR0SnPa25u1tVXX63rr7/eweoAwHl0ZgEAAOBazJkFAACAaxFmAQAA4FqEWQAAALgWYRYAAACuRZgFAACAaxFmAQAA4FqEWQAAALgWYRYAAACuRZgFAACAa/1/BKJP7P48p8IAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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IYzmSqDKykotX75cDz74oCTJsiwVFRXp61//uu6++26bqwMS5/Tp05oxY4ZefPFFXXHFFXaXAyREZ2enli5dql/+8pf68Y9/rLKyMj3wwAN2lwWMq7vvvluvvPKKXnrpJbtLuSA6swnS39+v2tparVmzZuiY2+3WmjVrtHPnThsrAxKvvb1dkpSTk2NzJUDirF+/XmvXrg37PQ84zZ///GctW7ZMN9xwg2bMmKHy8nJt3brV7rLCEGYTpLW1VcFgUHl5eWHH8/Ly1NzcbFNVQOJZlqU777xTl19+uRYtWmR3OUBCPPnkk6qrq9PmzZvtLgVIqPr6ev3qV7/SvHnz9Le//U133HGHvvGNb+jxxx+3u7QhXrsLAOAs69ev14EDB/Tyyy/bXQqQEE1NTfrmN7+pHTt2yO/3210OkFCWZWnZsmW69957JUnl5eU6cOCAtmzZoltuucXm6s6jM5sgubm58ng8amlpCTve0tKi/Px8m6oCEmvDhg16/vnn9c9//lOzZs2yuxwgIWpra3Xq1CktXbpUXq9XXq9XL774on7xi1/I6/UqGAzaXSIwbgoKCrRgwYKwYyUlJWpsbLSpomiE2QRJT09XRUWFampqho5ZlqWamhqtWrXKxsqA8RcKhbRhwwY988wz+sc//qG5c+faXRKQMFdffbX279+vffv2Db0tW7ZMN910k/bt2yePx2N3icC4ufzyy6NGLb755psqLi62qaJoLDNIoOrqat1yyy1atmyZVqxYoQceeEBdXV269dZb7S4NGFfr16/XE088oeeee06ZmZlD68Kzs7OVkZFhc3XA+MrMzIxaDz558mRNmzaNdeJwnLvuukurV6/Wvffeqy984QvatWuXHnnkET3yyCN2lzaE0VwJ9uCDD+r//u//1NzcrLKyMv3iF79QZWWl3WUB48rlcsU8/thjj+krX/lKcosBbHDllVcymguO9fzzz2vjxo06evSo5s6dq+rqaq1bt87usoYQZgEAAGAs1swCAADAWIRZAAAAGIswCwAAAGMRZgEAAGAswiwAAACMRZgFAACAsQizAAAAMBZhFgAAAMYizAKAQc6cOaMZM2bonXfeGfa81tZWzZgxQ8ePH09OYQBgE+4ABgAGqa6uVkdHh7Zu3Triud/61rfU1tam3/zmN0moDADsQZgFAEN0d3eroKBAf/vb37Ry5coRzz948KAqKip08uRJ5eTkJKFCAEg+lhkAgCH+8pe/yOfzDQXZtrY23XTTTZo+fboyMjI0b948PfbYY0PnL1y4UIWFhXrmmWfsKhkAEs5rdwEAgNF56aWXVFFRMfT+97//fR06dEh//etflZubq7feeks9PT1hj1mxYoVeeukl3XbbbckuFwCSgjALAIZoaGhQYWHh0PuNjY0qLy/XsmXLJElz5syJekxhYaH27t2brBIBIOlYZgAAhujp6ZHf7x96/4477tCTTz6psrIyfec739Grr74a9ZiMjAx1d3cns0wASCrCLAAYIjc3V21tbUPvX3vttWpoaNBdd92lkydP6uqrr9a3vvWtsMecPXtW06dPT3apAJA0hFkAMER5ebkOHToUdmz69Om65ZZbtG3bNj3wwAN65JFHwj5+4MABlZeXJ7NMAEgqwiwAGKKqqkoHDx4c6s5u2rRJzz33nN566y0dPHhQzz//vEpKSobO7+7uVm1tra655hq7SgaAhCPMAoAhFi9erKVLl+rpp5+WJKWnp2vjxo1asmSJrrjiCnk8Hj355JND5z/33HOaPXu2Pvaxj9lVMgAkHDdNAACDvPDCC/r2t7+tAwcOyO0evh+xcuVKfeMb39CXvvSlJFUHAMnHaC4AMMjatWt19OhRnThxQkVFRRc8r7W1VZ///Of1xS9+MYnVAUDy0ZkFAACAsVgzCwAAAGMRZgEAAGAswiwAAACMRZgFAACAsQizAAAAMBZhFgAAAMYizAIAAMBYhFkAAAAYizALAAAAY/1/pStQ6JZ8bTEAAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The 'waituntil' segment fills up a part of the blueprint with zeros\n", + "\n", + "# Example: a square pulse, then waiting until 5 s exactly and then a new sine\n", + "\n", + "bp_wait = bb.BluePrint()\n", + "bp_wait.setSR(100)\n", + "\n", + "bp_wait.insertSegment(0, ramp, (0, 0), dur=1)\n", + "bp_wait.insertSegment(1, ramp, (1, 1), name=\"plateau\", dur=1)\n", + "# function must be sthe string 'waituntil', the argument is the ABSOLUTE time to wait until\n", + "bp_wait.insertSegment(2, \"waituntil\", (5,))\n", + "bp_wait.insertSegment(3, sine, (1, 0.1, 0, -np.pi / 2), dur=1)\n", + "plotter(bp_wait)\n", + "\n", + "# If we make the square pulse longer, the sine still occurs at 5 s\n", + "bp_wait.changeDuration(\"plateau\", 1.5)\n", + "plotter(bp_wait)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Elements\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "Elements are containers containing blueprints. A valid element consists of blueprints that all have the same number of points and the same overall duration." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:21.923393Z", + "iopub.status.busy": "2025-01-21T06:30:21.923190Z", + "iopub.status.idle": "2025-01-21T06:30:22.045189Z", + "shell.execute_reply": "2025-01-21T06:30:22.044719Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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TZ0NUzTryUNVhCkK+VNnw+jzNxz4VGE77D6fKhBRaAiYNHOEnKDT+fY0M2mmILjopHnTpk246667kJ6ejocffhg333wzvvzyy5GITdUcfT2o27fNb4D8iMho5HHSkrCgjzAid8osmGLjvZa7nf2o278DfV0d53glEY1WvsPNabRajpmvAP+eZo6eQYG99957+N3vfjes1w4raW5sbMRzzz2HoqIiXHHFFaiursZrr72GxsZGrFu3zmu6Qho6e2836vbvgKvf4bXcGB2DvKllfAo7jOgNEcidPBORPmU2ksuJ+gM70ctZrYhCitvl9PqZ9czK8G1n3xFMiM545JFHcPfddw/rtUP+b7722mvx73//GykpKbjjjjtwzz33YPz48cN6c/Jn7+lC/YEdcDu9T7zGmLiBYckMEYIio2DRnX6g88TB3V5Tv8puN05U7EL2pOmISUwRGCERXSjfB9BYmqEMjp5BF+rw4cPDfu2Qk2aDwYC//OUvuP7666HjyWBE9XV14ETFbkg+PRWRcQnImTQDugDTllN40J4eOtB34hpZknDy4G5kTZiK2BROCUs02vmWBfgOMUnB4fewpSxDcrt500IjashJ8z//+c9gxKF6fdb2gYTZ5+44Mj6RM8aphFanQ3ZxKRoO70P3KYtnuSxJOHloL7ImTEFcaobACIloMP5jNDNpU0KgMhjJ7WL7q1xnZye2b9+OlpYWSGdNLAYAd9xxx5D3N+xiK7vdjtdffx2fffZZwGB279493F1ftDVr1uDnP/85mpubMXXqVLz++usoKysTFs9gejpODcwU59NDEZ2YjOzi6fynVxGNVousCVPRdLQCVkvj1ytkGQ2H90OSJCSYs8QFSETn5TsTnY41zYoIdJ10u5ycaVXF3n//fSxatAg9PT2Ii4uDRqPxrNNoNMomzYsXL8bHH3+MW265BWVlZV7BiPTuu+9i+fLlWLt2LWbPno3Vq1ejvLwcVVVVSEtLEx2en572Vr+plYGB2eOyiqfxqWsV0mi1yCiaDI1Gi87mk1+vkGU0VR2ALElIzMgRFyARnZN/TTOTZiVotTpotN5jG3AEDXVbsWIF7rnnHjz33HOIihqZEceG/d/8wQcf4MMPP8TcuXNHJJCR8sorr2DJkiWeJyPXrl2L9evX480338Sjjz4qODpvXa3NaKza75cwx6aYkTVhqt8J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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 1\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=0.1e-6)\n", + "bp_square.insertSegment(1, ramp, (10e-3, 10e-3), name=\"top\", dur=0.1e-6)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=0.1e-6)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 25e-3, 0, 0), dur=0.3e-6)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "# Now we create an element and add the blueprints to channel 1 and 3, respectively\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "\n", + "# We can check the validity of the element\n", + "elem1.validateDurations() # raises an ElementDurationError if something is wrong. If all is OK, does nothing.\n", + "\n", + "# And we can plot the element\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:22.047009Z", + "iopub.status.busy": "2025-01-21T06:30:22.046685Z", + "iopub.status.idle": "2025-01-21T06:30:22.050189Z", + "shell.execute_reply": "2025-01-21T06:30:22.049649Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Designated channels: [1, 3]\n", + "Total duration: 6e-07 s\n", + "Sample rate: 1000000000.0 (Sa/S)\n" + ] + } + ], + "source": [ + "# An element has several features\n", + "print(f\"Designated channels: {elem1.channels}\")\n", + "print(f\"Total duration: {elem1.duration} s\")\n", + "print(f\"Sample rate: {elem1.SR} (Sa/S)\")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:22.051840Z", + "iopub.status.busy": "2025-01-21T06:30:22.051673Z", + "iopub.status.idle": "2025-01-21T06:30:22.216786Z", + "shell.execute_reply": "2025-01-21T06:30:22.216182Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# We can modify the blueprints of an element through the element object\n", + "\n", + "# Change the sine freq\n", + "elem1.changeArg(\n", + " 3, \"sine\", \"freq\", 6.67e6\n", + ") # Call signature: channel, segment name, argument, new_value\n", + "\n", + "# make the second plateaus last longer\n", + "elem1.changeDuration(\n", + " 1, \"top2\", 0.2e-6\n", + ") # In this blueprint, the second top is called top2\n", + "elem1.changeDuration(3, \"top\", 0.2e-6)\n", + "\n", + "plotter(elem1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Sequences\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "Finally, we have reached the top level of the module: sequences. Unsurprisingly, sequences are containers containing elements. All elements in a sequence must specify the same channels." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:22.218956Z", + "iopub.status.busy": "2025-01-21T06:30:22.218641Z", + "iopub.status.idle": "2025-01-21T06:30:22.225154Z", + "shell.execute_reply": "2025-01-21T06:30:22.224597Z" + } + }, + "outputs": [], + "source": [ + "seq1 = bb.Sequence()\n", + "\n", + "# We fill up the sequence by adding elements at different sequence positions.\n", + "# A valid sequence is filled from 1 to N with NO HOLES, i.e. if position 4 is filled, so must be position 1, 2, and 3\n", + "\n", + "#\n", + "# Make blueprints, make elements\n", + "\n", + "# Create the blueprints\n", + "bp_square = bb.BluePrint()\n", + "bp_square.setSR(1e9)\n", + "bp_square.insertSegment(0, ramp, (0, 0), dur=100e-9)\n", + "bp_square.insertSegment(1, ramp, (1e-3, 1e-3), name=\"top\", dur=100e-9)\n", + "bp_square.insertSegment(2, ramp, (0, 0), dur=100e-9)\n", + "bp_boxes = bp_square + bp_square\n", + "#\n", + "bp_sine = bb.BluePrint()\n", + "bp_sine.setSR(1e9)\n", + "bp_sine.insertSegment(0, sine, (3.333e6, 1.5e-3, 0, 0), dur=300e-9)\n", + "bp_sineandboxes = bp_sine + bp_square\n", + "\n", + "# create elements\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp_boxes)\n", + "elem1.addBluePrint(3, bp_sineandboxes)\n", + "#\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(3, bp_boxes)\n", + "elem2.addBluePrint(1, bp_sineandboxes)\n", + "\n", + "# Fill up the sequence\n", + "seq1.addElement(1, elem1) # Call signature: seq. pos., element\n", + "seq1.addElement(2, elem2)\n", + "seq1.addElement(3, elem1)\n", + "\n", + "# set its sample rate\n", + "seq1.setSR(elem1.SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:22.226944Z", + "iopub.status.busy": "2025-01-21T06:30:22.226573Z", + "iopub.status.idle": "2025-01-21T06:30:22.229652Z", + "shell.execute_reply": "2025-01-21T06:30:22.229205Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Help on method changeArg in module broadbean.element:\n", + "\n", + "changeArg(channel: 'str | int', name: 'str', arg: 'str | int', value: 'int | float', replaceeverywhere: 'bool' = False) -> 'None' method of broadbean.element.Element instance\n", + " Change the argument of a function of the blueprint on the specified\n", + " channel.\n", + " \n", + " Args:\n", + " channel: The channel where the blueprint sits.\n", + " name: The name of the segment in which to change an argument\n", + " arg: Either the position (int) or name (str) of\n", + " the argument to change\n", + " value: The new value of the argument\n", + " replaceeverywhere: If True, the same argument is overwritten\n", + " in ALL segments where the name matches. E.g. 'gaussian1' will\n", + " match 'gaussian', 'gaussian2', etc. If False, only the segment\n", + " with exact name match gets a replacement.\n", + " \n", + " Raises:\n", + " ValueError: If the specified channel has no blueprint.\n", + " ValueError: If the argument can not be matched (either the argument\n", + " name does not match or the argument number is wrong).\n", + "\n" + ] + } + ], + "source": [ + "help(seq1.element(1).changeArg)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:22.231101Z", + "iopub.status.busy": "2025-01-21T06:30:22.230940Z", + "iopub.status.idle": "2025-01-21T06:30:22.456994Z", + "shell.execute_reply": "2025-01-21T06:30:22.456426Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# The sequence can be validated\n", + "seq1.checkConsistency() # returns True if all is well, raises errors if not\n", + "\n", + "# And the sequence can (if valid) be plotted\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Tektronix AWG 5014 output\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "The sequence object can output a tuple matching the call signature of the QCoDeS Tektronix AWG 5014 driver.\n", + "\n", + "For the translation from voltage to AWG unsigned integer format to be correct, the voltage ranges and offsets of the AWG channels must be specified (NB: This will **NOT** work if the channels on the AWG are in high/low mode).\n", + "\n", + "Furthermore, the AWG sequencer options should be specified for each sequence element." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:22.458890Z", + "iopub.status.busy": "2025-01-21T06:30:22.458577Z", + "iopub.status.idle": "2025-01-21T06:30:22.711497Z", + "shell.execute_reply": "2025-01-21T06:30:22.710887Z" + }, + "scrolled": false + }, + "outputs": [ + { + "data": { + "image/png": "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l3uMVG8A1h3WncwxUt1hvcmPesOW4o9UVFTAYrEongTt6elBdXW1RqXKnPXr1+P555/HK6+8ghkzZsSWV1dXIxwOw+fzyT4/+XdXV1cn3C4n39OjPXv2oLe3F+eccw6sViusVit27NiBRx99FFarFV6vNy9/NwDU1NTgjDPOkC1buHAhWltbAXxc9lPt69XV1ejt7ZW9H41GMTg4KPvtrDc+2efzYf9h3WHdyQSj1R3WG3XqTbYxKf6I3W7H0qVLsX379tgyURSxfft2nH/++RqWLD2SJGH9+vX4zW9+g5dffhkzZ86Uvb906VLYbDbZ7z58+DBaW1tjv/v888/H+++/L9th//znP8Ptdisqgl5cdNFFeP/997F3797Yv2XLluHaa6+N/T8ffzcAXHjhhYohkD744AM0NjYCAGbOnInq6mrZb/f7/Xj77bdlv93n82HPnj2xz7z88ssQRRErVqyILWO9yb/9h3WHdScdRq07rDfq1JusU+2Rvhzwi1/8QnI4HNIzzzwjHThwQPrqV78qlZSUyJ4EzTW33nqr5PF4pFdffVXq6uqK/RsbG4t95mtf+5rU0NAgvfzyy9Lu3bul888/Xzr//PNj758cJubiiy+W9u7dK/3xj3+UKisrdT9MTLzJTwJLUv7+7l27dklWq1XatGmTdOTIEelnP/uZ5HK5pP/6r/+KfebBBx+USkpKpOeee0567733pM997nMJh8dpbm6W3n77bemNN96Q5s6dO+WwUqw3+bP/JMK6w7ozXaw7H2O9yU69ySYmxXEee+wxqaGhQbLb7dK5554rvfXWW1oXKS0AEv57+umnY58JBoPS17/+dam0tFRyuVzS3//930tdXV2y9Zw4cUK69NJLJafTKVVUVEi33367FIlEVP416Yk/QOXz7/79738vLVq0SHI4HNKCBQukJ598Uva+KIrSd7/7Xcnr9UoOh0O66KKLpMOHD8s+MzAwIH3pS1+SioqKJLfbLd1www3SyMhIwnisN/m1/8Rj3fkY686pse58jPXmY5muN9likiRJUq9dmoiIiIhIf9inmIiIiIgMj0kxERERERkek2IiIiIiMjwmxURERERkeEyKiYiIiMjwmBQTERERkeExKSYiIiIiw2NSTERERESGx6SYsmpgYABVVVU4ceJE2uv64x//iLPPPhuiKKZfMCKdY90hSh7rDaWDSTFl1aZNm/C5z30OTU1Naa/rkksugc1mw89+9rP0C0akc6w7RMljvaF0MCmmrBkbG8N//Md/4KabbsrYOteuXYtHH300Y+sj0iPWHaLksd5QupgUU9Zs27YNDocD5513HgDg1Vdfhclkwvbt27Fs2TK4XC5ccMEFOHz4cOw77777Lj796U+juLgYbrcbS5cuxe7du2Pvf/azn8Xu3bvx4Ycfqv57iNTCukOUPNYbSheTYsqa119/HUuXLlUsv/vuu/Hwww9j9+7dsFqtuPHGG2PvXXvttZgxYwbeeecd7NmzB3fddRdsNlvs/YaGBni9Xrz++uuq/AYiLbDuECWP9YbSZdW6AJS/WlpaUFtbq1i+adMmrFy5EgBw11134fLLL8f4+DgKCgrQ2tqKO+64AwsWLAAAzJ07V/H92tpatLS0ZLfwRBpi3SFKHusNpYstxZQ1wWAQBQUFiuVnnXVW7P81NTUAgN7eXgDAhg0b8JWvfAWrV6/Ggw8+mPCWldPpxNjYWJZKTaQ91h2i5LHeULqYFFPWVFRUYGhoSLF88q0pk8kEALEhb/75n/8Z+/fvx+WXX46XX34ZZ5xxBn7zm9/Ivj84OIjKysoslpxIW6w7RMljvaF0MSmmrGlubsaBAweS/t68efPwrW99Cy+++CK+8IUv4Omnn469Nz4+jg8//BDNzc2ZLCqRrrDuECWP9YbSxaSYsmbNmjXYv39/wiv3RILBINavX49XX30VLS0t+Mtf/oJ33nkHCxcujH3mrbfegsPhwPnnn5+tYhNpjnWHKHmsN5QuJsWUNYsXL8Y555yD//t//++0Pm+xWDAwMIDrrrsO8+bNwz/8wz/g0ksvxf333x/7zH//93/j2muvhcvlylaxiTTHukOUPNYbSpdJkiRJ60JQ/nrhhRdwxx13YN++fTCb07sG6+/vx/z587F7927MnDkzQyUk0ifWHaLksd5QOjgkG2XV5ZdfjiNHjqCjowP19fVprevEiRP48Y9/zIMTGQLrDlHyWG8oHWwpJiIiIiLDY59iIiIiIjI8JsVEREREZHhMiomIiIjI8JgUExEREZHhMSkmIiIiIsNjUkxEREREhsekmIiIiIgMj0kxERERERkek2IiIiIiMrz/H3t4rLmZFrtUAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "seq1.setChannelAmplitude(1, 10e-3) # Call signature: channel, amplitude (peak-to-peak)\n", + "seq1.setChannelOffset(1, 0)\n", + "seq1.setChannelAmplitude(3, 10e-3)\n", + "seq1.setChannelOffset(3, 0)\n", + "\n", + "# Here we repeat each element twice and then proceed to the next, wrapping over at the end\n", + "seq1.setSequencingTriggerWait(1, 0)\n", + "seq1.setSequencingNumberOfRepetitions(1, 2)\n", + "seq1.setSequencingEventJumpTarget(1, 0)\n", + "seq1.setSequencingGoto(1, 2)\n", + "#\n", + "seq1.setSequencingTriggerWait(2, 0)\n", + "seq1.setSequencingNumberOfRepetitions(2, 2)\n", + "seq1.setSequencingEventJumpTarget(2, 0)\n", + "seq1.setSequencingGoto(1, 3)\n", + "#\n", + "seq1.setSequencingTriggerWait(3, 0)\n", + "seq1.setSequencingNumberOfRepetitions(3, 2)\n", + "seq1.setSequencingEventJumpTarget(3, 0)\n", + "seq1.setSequencingGoto(3, 1)\n", + "\n", + "# then we may finally get the \"package\" to give the QCoDeS driver for upload\n", + "package = seq1.outputForAWGFile()\n", + "\n", + "# Note that the sequencing information is included in the plot in a way mimicking the\n", + "# way the display of the Tektronix AWG 5014\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:22.713298Z", + "iopub.status.busy": "2025-01-21T06:30:22.713023Z", + "iopub.status.idle": "2025-01-21T06:30:22.717525Z", + "shell.execute_reply": "2025-01-21T06:30:22.716957Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "[1, 3]" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The package is a SLICEABLE object\n", + "# By slicing and indexing it, one may retrieve different parts of the sequence\n", + "\n", + "chan1_awg_input = package[0] # returns a tuple yielding an awg file with channel 1\n", + "chan3_awg_input = package[1] # returns a tuple yielding an awg file with channel 3\n", + "\n", + "both_chans_awg_input = package[\n", + " :\n", + "] # returns a tuple yielding an awg file with both channels\n", + "\n", + "# This may be useful to make one big sequence for one experiment and then uploading part of it to one awg\n", + "# and part of it to another (since physical awg's usually don't have enough channels for a big experiment)\n", + "\n", + "# To see how the channels are counted, look up the channels\n", + "package.channels" + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:22.719185Z", + "iopub.status.busy": "2025-01-21T06:30:22.718859Z", + "iopub.status.idle": "2025-01-21T06:30:22.721216Z", + "shell.execute_reply": "2025-01-21T06:30:22.720776Z" + } + }, + "outputs": [], + "source": [ + "## Example of uploading the sequence (requires having qcodes installed, see https://github.com/QCoDeS/Qcodes)\n", + "\n", + "# from qcodes.instrument_drivers.tektronix.AWG5014 import Tektronix_AWG5014\n", + "# awg = Tektronix_AWG5014('AWG1', 'TCPIP0::172.20.3.57::inst0::INSTR', timeout=40)\n", + "# awg.make_send_and_load_awg_file(*package[:])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Delays and filter compensation\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "In a real experimental setting, the signal transmission line may distort and/or delay the pulse sequence. The Sequence object can perform some compensation for this when making the `.awg` file." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:22.722971Z", + "iopub.status.busy": "2025-01-21T06:30:22.722655Z", + "iopub.status.idle": "2025-01-21T06:30:22.729264Z", + "shell.execute_reply": "2025-01-21T06:30:22.728845Z" + } + }, + "outputs": [], + "source": [ + "# To delay channel 1 with respect to the other channels, set its delay\n", + "seq1.setChannelDelay(1, 0)\n", + "seq1.setChannelDelay(3, 123e-9)\n", + "\n", + "# To apply for a high pass filter with a cut-off frequency of 1 MHz on channel 3, we can do\n", + "seq1.setChannelFilterCompensation(3, \"HP\", order=1, f_cut=1e6)\n", + "# or, equivalently,\n", + "seq1.setChannelFilterCompensation(3, \"HP\", order=1, tau=1e-6)\n", + "\n", + "# Note that setting the filter compensation may invalidate the sequence in the sense that the specified voltage ranges\n", + "# on the AWG may have become too small. The function outputForAWGFile will warn you if this is the case.\n", + "\n", + "newpackage = seq1.outputForAWGFile()" + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:22.730849Z", + "iopub.status.busy": "2025-01-21T06:30:22.730525Z", + "iopub.status.idle": "2025-01-21T06:30:22.984223Z", + "shell.execute_reply": "2025-01-21T06:30:22.983753Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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X3eMXm9kmXUnF+uO2euNLBu/7uRbvVGLIMuyrHXrNBERERFRLmGfYiIiIiLKewyKiYiIiCjvMSgmIiIiorzHoJiIiIiI8h6DYiIiIiLKewyKiYiIiCjvMSgmIiIiorzHoJiIiIiI8h6DYlJVT08PKisrcfTo0YzTevXVV3HmmWdCkqTMC0aU41h3iKaO9YYywaCYVPXwww/j85//PGbMmJFxWpdccgnsdjt+9rOfZV4wohzHukM0daw3lAkGxaSaoaEh/Md//AduueWWrKV544034sknn8xaekS5iHWHaOpYbyhTDIpJNZs2bYLT6cQ555wDAHjrrbcgCAI2b96MZcuWwe1247zzzsO+ffvin/noo4/wt3/7tygqKoLX68XSpUuxffv2+Puf+9znsH37dhw6dEjz70OkFdYdoqljvaFMMSgm1bzzzjtYunRp0vZ7770Xjz/+OLZv3w6bzYabb745/t51112HadOm4YMPPsCOHTtwzz33wG63x99vaGhAVVUV3nnnHU2+A5EeWHeIpo71hjJl07sAZF5NTU2ora1N2v7www9j5cqVAIB77rkHl19+OYaHh1FQUIDm5mbcddddmD9/PgBgzpw5SZ+vra1FU1OTuoUn0hHrDtHUsd5QpthSTKoJhUIoKChI2n7GGWfE/7+mpgYA0NnZCQBYt24dvvKVr2DVqlV49NFHx3xk5XK5MDQ0pFKpifTHukM0daw3lCkGxaSa8vJy9Pb2Jm0f/WhKEAQAiE9588///M/49NNPcfnll+ONN97A6aefjt/85jeKz/v9flRUVKhYciJ9se4QTR3rDWWKQTGpprGxEbt3757y5+bOnYtvfetbeO2113D11Vfj+eefj783PDyMQ4cOobGxMZtFJcoprDtEU8d6Q5liUEyqWb16NT799NMx79zHEgqFsHbtWrz11ltoamrCn//8Z3zwwQdYsGBBfJ/3338fTqcT5557rlrFJtId6w7R1LHeUKYYFJNqFi9ejLPOOgv/9//+35T2t1qt6OnpwfXXX4+5c+fiH/7hH3DppZfioYceiu/z3//937juuuvgdrvVKjaR7lh3iKaO9YYyJciyLOtdCDKvV155BXfddRd27doFiyWze7Du7m7MmzcP27dvx8yZM7NUQqLcxLpDNHWsN5QJTslGqrr88stx4MABtLS0oL6+PqO0jh49ih/96Ec8OVFeYN0hmjrWG8oEW4qJiIiIKO+xTzERERER5T0GxURERESU9xgUExEREVHeY1BMRERERHmPQTERERER5T0GxURERESU9xgUExEREVHeY1BMRERERHmPQTERERER5b3/H6rSEFhJzt1XAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# For sanity checking, it may be helpful to see how the compensated waveforms look.\n", + "# The plotter function can display the delays and filter compensations\n", + "\n", + "plotter(seq1, apply_filters=True, apply_delays=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Sequences varying parameters\n", + "([back to ToC](#Table-of-Contents))\n", + "\n", + "The module contains a few wrapper functions to easily generate sequences with some parameter(s) varying throughout the sequence. First two examples where a Base element is provided and varied, then an example where an existing Sequence is repeated." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:22.986020Z", + "iopub.status.busy": "2025-01-21T06:30:22.985682Z", + "iopub.status.idle": "2025-01-21T06:30:23.244604Z", + "shell.execute_reply": "2025-01-21T06:30:23.244076Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 1: vary the duration of a square pulse\n", + "\n", + "# First, we make a basic element, in this example containing just a single blueprint\n", + "basebp = bb.BluePrint()\n", + "basebp.insertSegment(0, ramp, (0, 0), dur=0.5)\n", + "basebp.insertSegment(1, ramp, (1, 1), dur=1, name=\"varyme\")\n", + "basebp.insertSegment(2, \"waituntil\", 5)\n", + "basebp.setSR(100)\n", + "\n", + "baseelem = bb.Element()\n", + "baseelem.addBluePrint(1, basebp)\n", + "\n", + "plotter(baseelem)\n", + "\n", + "# Now we make a 5-step sequence varying the duration of the high level\n", + "# The inputs are lists, since several things can be varied (see Example 2)\n", + "channels = [1]\n", + "names = [\"varyme\"]\n", + "args = [\"duration\"]\n", + "iters = [[1, 1.5, 2, 2.5, 3]]\n", + "\n", + "seq1 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)\n", + "plotter(seq1)" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:23.246554Z", + "iopub.status.busy": "2025-01-21T06:30:23.246220Z", + "iopub.status.idle": "2025-01-21T06:30:23.504974Z", + "shell.execute_reply": "2025-01-21T06:30:23.504413Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsYAAAEaCAYAAAASfJF8AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAJWdJREFUeJzt3X9wFPX9x/HX3QWSqAT5GX6DDlEREEL4aafVwZSg2A51dByHDo4orQ6ZqqlasQpO7Uin30FwKhW0BaaDFOxUpRVLh4mD6BAngNIqEQqV8juBSAy/Qn7c3vcP5SCQS27v9vbn8zGTGbjsbd6377u91+5+djcUi8ViAgAAAAIu7HQBAAAAgBsQjAEAAAARjAEAAABJBGMAAABAEsEYAAAAkEQwBgAAACQRjAEAAABJBGMAAABAkpTldAF2MwxDR44cUZcuXRQKhZwuBxkWjUa1d+9eDR06VJFIxOlykGH0O1jod7A0NzeroqJCI0aMUFZW4OJL4BiGoZqaGhUWFtra78C9s44cOaKBAwc6XQYAAAA6UFlZqXHjxtn29wIXjLt06SJJOnjwoPLy8kw/PxqNasfH/5IkjZ44ytK9FJmct98lWnaHDh3S8OHDXdlvL9bhFn7vtxlerNks+p06L77GRDV/8cUXmjhxoiorK9W3b9+U5vvZts8lSSPHjvDEssgktyyPRHUcPXpU48ePV35+vq31BC4Ynx8+kZeXl/KK9Korr4rPw+pgnKl5+12iZXe+x27stxfrcAu/99sML9ZsFv1OnRdfY0f97tu3rwYMGJDSfI/tPy5JGjBggCeWRSa5ZXl0VEc4bO/pcJx8BwAAAIhgDAAAAEgiGAMAAACSCMYAAACApACefJdI3Z49MpqbO5zOMAydPHhQknTiixxLB4Vnct5+d37ZhSMRSYUdTu+Gfpvhljrcwu/9NsOLNZtFv1PnxddIv+3hluVhtt+ZRjD+ltHcLKOlpePpDEOxaPSbf7e0SBYH40zN2+/OLzsj2eld0G8z3FKHW/i932Z4sWaz6HfqvPga6bc93LI8zPY70wjG3wp36pTchIah0LeXEglnZVm7hZXJefvdt8sunOTlZlzRbzPcUodb+L3fZnixZrPod+q8+Brptz3csjxM9jvTCMbf6lZQkNR00WhUeSfOSZK6Dxtm+XWMMzVvv7t42SXDDf02wy11uIXf+22GF2s2i36nzouvkX7bwy3Lw2y/My3gm0sAAADANwjGAAAAgAjGAAAAgCSCMQAAACCJYAwAAABI4qoUcXv21Km5ueOr6BmGoQMHT0mScr84YfkNPjI1b787v+yyIiGNSWJ6N/TbDLfU4RZ+77cZXqzZLPqdOi++RvptD7csD7P9zjSC8beamw21tCT3wTKi30zX0mJYej3sTM7b784vu5YkD4K4od9muKUOt/B7v83wYs1m0e/UefE10m97uGV5mO13phGMv9WpU3INMQwpHPlm2qyssMV7jDM3b787v+yyIqGkpndDv81wSx1u4fd+m+HFms2i36nz4muk3/Zwy/Iw2+9MIxh/q6CgW1LTRaNRNZzoIkkaNqy75Tf4yNS8/e7iZZcMN/TbDLfU4RZ+77cZXqzZLPqdOi++RvptD7csD7P9zrRgby4BAAAA3yIYAwAAACIYAwAAAJIIxgAAAIAkgjEAAAAgiWAMAAAASCIYAwAAAJIIxgAAAIAkgjEAAAAgiWAMAAAASCIYAwAAAJIcDsabN2/WD37wA/Xr10+hUEjvvPNOh8/ZtGmTxowZo+zsbA0dOlQrV67MeJ0AAADwvywn//iZM2c0atQozZo1S3fddVeH0+/bt0/Tpk3Tww8/rDfeeEPl5eV66KGH1LdvX5WUlNhQ8TdamhrVfK5Bp2prFI5Yt21hRA01nKpXdu6Vls0TAAAAyXE0GN9+++26/fbbk55+6dKluuaaa7Rw4UJJ0rBhw/TRRx9p0aJFtgXj+urDOrpnpyTp8FVNCoctDMaGodoDhxSOZKl5/HBFrupi2bwBAADQPk+NMa6oqFBxcXGrx0pKSlRRUZHwOY2NjTp58mSrn3ScOLI/recnw4i2qL7mSMb/DgAAAC7wVDCurq5Wfn5+q8fy8/N18uRJNTQ0tPmcBQsWqGvXrvGfgQMHplVDtLkprecn/Xeizbb8HQAAAHzD0aEUdpg7d67Kysri/z958mTa4fhiWdk5CkfSX4wtTY0ymhotqAgAAACp8FQw7tOnj2pqalo9VlNTo7y8POXm5rb5nOzsbGVnZ2espn7XjdSV3XqkPZ/qvVX66tD/4v+PxWJpzxMAAADJ89RQikmTJqm8vLzVYxs3btSkSZMcqggAAAB+4WgwPn36tHbs2KEdO3ZI+uZybDt27NCBAwckfTMMYubMmfHpH374YX355Zd66qmntGvXLv3+97/Xm2++qccff9yJ8gEAAOAjjgbjbdu2qbCwUIWFhZKksrIyFRYWat68eZKko0ePxkOyJF1zzTVav369Nm7cqFGjRmnhwoX6wx/+YOs1jC8b4BCyaMYhq2YEAACAVDg6xvjWW29tdyxtW3e1u/XWW/Xpp59msCoAAAAEkafGGAMAAACZQjB2K65KAQAAYCuCMQAAACCCMQAAACCJYGweQxwAAAB8iWAMAAAAiGAMAAAASCIYpy1k0Y05QpbdKQQAAACpIBgDAAAAIhgDAAAAkgjGrsW1LwAAAOxFMAYAAABEMAYAAAAkEYxTwCAHAAAAPyIYp82iy6xxtTYAAABHEYwBAAAAEYwBAAAASQRj94oxlhkAAMBOBGMAAABABGPT2JELAADgTwRjAAAAQARjAAAAQBLBGAAAAJBEME6fVff3CHGHDwAAACcRjAEAAABJWU4XALhdS3OTmhvP6cyJWoUjzmxLGlFDDadPqnNOriN/HwCAICAYuxbXhXODk8eO6uh/PpckHbzinMJhh4KxYah2/yFJ0qnhg3V1fj9H6gAAwM8YSgG046vD/3O6hMuccGFNAAD4AcHYLO7wESjRpkanS7hMS3OT0yUAAOBLDKVIU8iqy1JYNh9kUqRTZ4VsHk4RMwwZjeds/ZsAAAQRwRgwof8No3Rltx62/s1TXx3Tgc+2XXiAoxYAAGQEQykAAAAAEYxdK8ZeQVegCwAABAfBGDDDgaHg3BURAAB7EIxNirEPEQAAwJcIxgAAAIAIxgAAAIAkgjHQPk6CBAAgMAjG6bLqxChOsAIAAHAUwRgAAAAQwRgwhUunAQDgXwRjt2JoKwAAgK0IxoDHcFdEAAAyI8vpAjyHUAL4VrSlRXVHD6rpXIP2d2lWOOz+fQeGYahm3z5ldeqkpjPXKTcvz+mSAMCzCMZpsmrMKSNX3YoNoSCp2Vul0yeOS5Ia6us8E4ybzp5Wk6SDVdtVMOFWxsIDQIrcv9YHXMWBwEHIsU3Dqa+dLiEtzeca1NzY4HQZAOBZBGMA+FbMMJwuIX0c5ACAlDGUwqVifLshEca526Zb/8HKzr3S6TISisViOrpnp9NlAIBvEIwBIIEu3Xvrym49nC4jIcOIEowBwEIMpQDawc7ZYPFHu/3xKgDACQRjAEjE5ec9htxeIAB4DMEYAAAAEMEYMMeJq7WxVxAAgKR98cUXuvbaa1N6LsHYLbhWLeA8Hwwq55bhAIKuqalJ+/fvT+m5XJUC8BhiDwAgyMrKytr9/fHjx1OeN8EYAAAAnvHyyy9r9OjRysvLa/P3p0+fTnneBGO34nCoO9CHQAu5fYiT2+sDgAwYOnSoHn/8cf34xz9u8/c7duxQUVFRSvNmjDFggiMnwpF9AACIGzt2rLZv357w96FQKOXzLdhjbBIntgAAADhn4cKFamxsTPj7UaNGyTCMlOZNME6XRYcyXX/IFu7BxhkAIMD69OmTsXkTjAEgzgcbHT54CXYyWlrU3HhODfV1Ckf8N7rQiBpqOndWnbJznS4F8IS0gnFzc7Oqq6t19uxZ9erVS927d7eqLsAVYqSMgHP3kRyONKXnVG2NDu/+tyRpf+5ZhcM+DMaGoZr/HlLn3CtlTBqtSCTidEmAq5leC5w6dUqvvvqqbrnlFuXl5WnIkCEaNmyYevXqpcGDB2v27NnaunVrJmoFAMAyXx380ukSbNPUcEZn62qdLgNwPVPB+KWXXtKQIUO0YsUKFRcX65133tGOHTv0n//8RxUVFZo/f75aWlo0ZcoUTZ06VXv27MlU3QAApKWlKfHJO34UtNcLpMLUUIqtW7dq8+bNGj58eJu/Hz9+vGbNmqWlS5dqxYoV+vDDD1VQUGBJoa7BiU/B5sChaw6X28cPH2+G/6QuHMlSKOyfz1u0pcXpEgDPMRWM//znPyc1XXZ2th5++OGUCgIAwAkDbizUld16OF2GZfb/q1KnGT6BAFq3bp3q6+s1c+ZM0881Pcb47rvv1oYNG7ieL+AYPnuAFYL2SQra60Vw/eIXv9ADDzyQ0nNNB+O6ujpNmzZNgwYN0rx58/Tll8E5eQFAwPjnqDqSQb8BX9i1a5ei0WhKzzUdjMvLy/Xll1/qwQcf1KpVq1RQUKDJkydr9erV7d6FxK8cuUUw7MOREQAAAiOl6xgPHjxYzz//vJ5//nm9//77Wr58uWbPnq3S0lLdd999mjVrloqKiqyuFXCcMyfCsfGFdnByJhLhvYEA+Prrr1VZWaljx45ddhvoVMYYp33nu8mTJ2vy5Mk6deqUVq9erWeeeUbLli1TC2fDAvAajhAES9D6HbTXC9/7+9//rhkzZuj06dPKy8trtfMqFAo5E4wlad++fVq5cqVWrlyp+vp6FRcXWzHbQOPkRiTCW8M+nhwqxRsEQED8/Oc/16xZs/Tiiy/qiiuusGSeKd//8ty5c1q1apUmT56sgoIC/elPf9KDDz6offv2acOGDZYUBwAAALTl8OHD+tnPfmZZKJZS2GNcWVmp5cuXa+3atTp37px+9KMfacOGDbrtttu4EQEA2Iz1rnVYloC3lJSUaNu2bbr22mstm6fpYDxx4kSNGjVKL7zwgmbMmKFu3bpZVownWbUeZYUMOI67xsFP+FqBH/3tb3+L/3vatGl68sknVVVVpZEjR6pTp06tpv3hD39oev6mgvGBAwe0bds2jRkzJqnpDx8+rP79+5suCgCAzAvahlDQXi/8aPr06Zc99qtf/eqyx0KhUErXMjY1xnjcuHFatmyZtm7dmnCa+vp6vf766xoxYoT++te/JjXfJUuWaMiQIcrJydGECRNUWVmZcNqVK1cqFAq1+snJyTHzMlLGCXFwBHt9AJvwYQPczjCMpH5SvcGHqT3GVVVVevHFF/X9739fOTk5KioqUr9+/ZSTk6O6ujpVVVVp586dGjNmjH7729/qjjvu6HCea9euVVlZmZYuXaoJEyZo8eLFKikp0e7du9W7d+82n5OXl6fdu3fH/+/LcWGEcAAAAFuZCsY9evTQwoUL9etf/1rr16/XRx99pP3796uhoUE9e/bUjBkzVFJSohEjRiQ9z5deekmzZ8+O39N66dKlWr9+vZYvX66nn366zeeEQiH16dPHTOmAf7DRZB8/bnQjQHj/XsyIRvV1zWE1n2vQwa6GwuGUL8zlC4Zh6Pj+L5VzVZ7TpZj2/vvvq7S0VB9//LHy8lrXX19fr5tvvlmvvvqqvve975med0rXMc7NzdXdd9+tu+++O5WnxzU1NWn79u2aO3du/LFwOKzi4mJVVFQkfN7p06c1ePBgGYahMWPG6MUXX9Tw4cPbnLaxsbHVrapPnjyZVs0AAH8I2jZm0IcD1vy3SqdqayRJZ+pqCcaGoXOnT+rc6ZP6+shB9Rg4xOmSkrZ48WLNnj37slAsSV27dtVPf/pTLVq0KKVg7Oi7ora2VtFoVPn5+a0ez8/PV3V1dZvPuf7667V8+XKtW7dOq1atkmEYuvnmm3Xo0KE2p1+wYIG6du0a/xk4cKDlrwMAALjb2fo6p0twrTMnTzhdgin/+te/NHXq1IS/nzJlirZv357SvD23uTRp0iTNnDlTo0eP1i233KK33npLvXr10rJly9qcfu7cuaqvr4//HDx40OaK4WWX7WFx4NC6J+++5lU+2KMW9L2CQCIxw3C6BPfy2HqjpqbmskuzXSwrK0vHjx9Pad6W3BI6VT179lQkElFNTU2rx2tqapIeQ9ypUycVFhZq7969bf4+Oztb2dnZadcqyXNvHADp8eWJvUiMdgfK1X0HqnOudXdM85IzX5+IDyvxov79++vzzz/X0KFD2/z9v//9b/Xt2zeleTsajDt37qyioiKVl5fHr0tnGIbKy8tVWlqa1Dyi0ag+++yzpK6AkQlWfXGyVxDJ4iYUAGDepWvOvJ59dGW3Ho7U4rSYYXg6GN9xxx167rnnNHXq1Msu2dvQ0KD58+frzjvvTGnejgZjSSorK9P999+vsWPHavz48Vq8eLHOnDkTv0rFzJkz1b9/fy1YsEDSNxdxnjhxooYOHaqvv/5a//d//6f9+/froYcecvJlAIAz2KudOr8fBbz0veHzl2saH504rw3BevbZZ/XWW2/puuuuU2lpqa6//npJ0q5du7RkyRJFo1H98pe/TGnejgfje++9V8ePH9e8efNUXV2t0aNHa8OGDfET8g4cONDqzNG6ujrNnj1b1dXV6tatm4qKirRlyxbdeOONTr0E+JnHVhYAUseRO8Ab8vPztWXLFj3yyCOaO3duPNiHQiGVlJRoyZIll13YIVmOB2NJKi0tTTh0YtOmTa3+v2jRIi1atMiGqpxFHAOQEjbmgLbx2bjAB0eaBg8erPfee091dXXau3evYrGYCgoK1K1bt7Tm64pgDAAArOX96APbeHijoVu3bho3bpxl8/Pc5doAALBC0E5kDdrrBVJBME6bRdvkbNp7giNjEH1wyAsA3IbLMaItBGPAazx8yAvW44QxIFmsO9ExgrEJHIYC/M1rlyyCxdiDiIBggzoxgrFb8QUNOM+DQYkNeCBZ3vt8ZwprjQsIxoAZjgwxZuUNZITfd0Cw7mjF7+2GNQjGAOBlhB/L+H4jlGSI83z+Vk8HwRhIgPGmAIBA4PsujmAMAOf54cvBBy8B1vD9HvB0sXjQBoKxGXzhAAAA+BbBGAAA+J8fjghZhKMJiRGM02XVje94kwKuw7U+Af/i830xNhrOIxgDibB3AQCAQCEYAyawZx9uw1sSSA43v7kYK45ECMauxQcYbeMycoA1+CwB3+CjcAHBGAAS8eROFb7hUubz3e9sCAAdIxgDgAgNQOD4fEMIqSEYp8m6s1r5gHqDA31i5Y128f5AAqw7WmPjF0kgGJvChwoAfCNoQSlorxeJsdGUEMEYSMC1ZzDz5YZ2MCQESA5XGboI6404grFL8R4FbMaHDgACj2AMAAmwRylYuBMagoJ3emIEYwAAgABz7dBBBxCMAcDL2KuNBNgDDphHMDaBk1oChnYDweLzHMkqDegYwRgww4nLGPv929rVWPZ+xY6O4KHnF+FIU0IE43RZ9ebiTQrACnz34zy+VtrH9+4FbDTEEYwBQJx8AgAgGAOAp7HPC0kL+l7BoL9+JIVg7FZ8gNEOxsoBaeIzhADjGu2JEYwBExw5EY71F0xgSEjqCAsACMZAQgSMQKHdQKBwxR+0hWCcJqv2MPDxBFyIDyY8jD3ggHkEYzPYowQAPub3IMmXGNARgjHgRZw4hPPYK5gSxmIHS5snLPPRieOE7gsIxi7FShsAkB6SH2AWwRhIgC1oePLkHN63ADrCkaaECMaAGQ6sTDiBxi4ESvhboI9EssHYPpZPHMEYADyMDacUkQMCj88O2kIwNiHQW9sA4HfkJCDwCMYAAACACMbuwSEdmMDRC+txsiV8h++VuLbXmcFdPpeeWMza7wKCMWCCM2PSgrvydhzBAn5C+gE6RDAGEuFLBPCxyz/gnrw8HwBLEYzdisO6AIA0EPMvwldqa5e+OcgccQRjAAAAQARjwJvYuAdgEiftXoJd6mgDwThdFp2cw4XGAffx4ueSq2skh+UEoC0EYzNYkQYKe1cChnbDgxtCQCq8uNFvF4Ix4Hasv9AevuCAJHAVkvaxZ+A8gjEAAH7ERhNgGsEYAAAgUNhoSoRgDADy0ZhyzoVIThuLyffjLgP83uBky/axeC4gGAOexFoMQPt8H/TTxfJBGwjGgBkOrEg5QQQAAHsQjAHAw9hwAgDrEIzTxJeSjzHoCvAt34wpR3KCOKa8PZe+dL7v4gjGAJBIkL84ASCACMaAB3GGdQb4ZJmyJxQAUkcwdimCD4CksFcb6BAbjK1dOgyU5XMBwRgAAAAQwRgwxZGTLdkjCFivraNyPv+scSQS6BjB2IQ2VypWrUd9vkIGYBOyD87jewUwjWAMAAAAiGAMAAlxnXL4SpCHUgRw6Ey7Ln3tQX5vXIJg7Fa8SdEe3h4AAFiOYAyY4ci5dwHeq4EO8fawDkcIABCMAQAAAoQdLokRjM1geEOgcGkjsAMRXsYe8PaxfNAWgjEAyE8bQn55HQBgP4Ix4EHcvhMXsNcrFRm9Lr1LsdYAOkYwTpNV43Q4pAMAsBRfK3FB3BAywz9HzNJHMAYAAABEMAYSa2MLmjN5fcwne0zY8wMAqSMYuxRfbYDz2BDysSBuQATxNZ8X5NfeFu58lxDBGAAAsSEEwCXBeMmSJRoyZIhycnI0YcIEVVZWtjv9X/7yF91www3KycnRyJEj9d5779lUKQC4DGEOSAkbQmhLltMFrF27VmVlZVq6dKkmTJigxYsXq6SkRLt371bv3r0vm37Lli267777tGDBAt15551avXq1pk+frk8++UQjRoww/fdjhqGWpsakpm1uPNfGoxZ9sC49qmEYaj7XYM28A8CIGmppapIkRVtaFIlE2pzOVL+b2uq3O7Q0nlMsGnW6DMcErd9mRJubfbfuoN+puTT4GdEWT7w36Lf9YrGYY++NZPttF8eD8UsvvaTZs2frgQcekCQtXbpU69ev1/Lly/X0009fNv3LL7+sqVOn6sknn5QkvfDCC9q4caNeeeUVLV261PTfb2o4qy+3f5Tei8iAprOntbfyA6fL8AzDMHR0zyFJUt21PZV/7XVtTpd+vx3Yw9DGXo19n2yxvw4X8XW/01Tz3y9U898vnC7DUvb1299O1dbozInjTpfRIfptv2hzk2OZI9l+28XRoRRNTU3avn27iouL44+Fw2EVFxeroqKizedUVFS0ml6SSkpKEk7f2NiokydPtvoBAOBy3tsQQjqC22/unZCYo8G4trZW0WhU+fn5rR7Pz89XdXV1m8+prq42Nf2CBQvUtWvX+M/AgQOtKV5SVudshS3a5d855wpL5oPMsbLfZkSyOinSqZPtfzfonOq3WZ1zWXdYwSv9NqNz7pVOl+Bafuy3Gaw3EnPFyXeZNHfuXNXX18d/Dh48aMl8I1md1LfA/JjmRHLzrlb3AUMUCvu+JZ5kdb/NCIVC6jN0uMJZhGO7RLI6qc/Q4U6XkZTeg69Tp+xcp8vwNCc/35mU16uvuvTqw0lml/Brv83olJOrXkOuI3O0wdExxj179lQkElFNTU2rx2tqatSnT582n9OnTx9T02dnZys7OzthDZ2vuFLXf6c44e8vFY0aOq1PFQ5HdGX3nkk/Lxm9r7le/W84o1jM0HU3FyoS4Q2brPN9kaQeA69NOJ2b+m1Gl5756n/9SBlGlPeG7On3VT16pV2nHTrl5qrP0GEyDEPX3Tzal+8Nv3++MyUciaj/DaN0tLZFisU8s+6g3/boMfAa9b+hzvHMkWy/7eJoMO7cubOKiopUXl6u6dOnS/pmEHZ5eblKS0vbfM6kSZNUXl6uxx57LP7Yxo0bNWnSpJRqCIVCCkWSXwwxRRUOZ+7wSygUUigUUTgSCfRhHrMu7ksonHjviNv6bVY4zHtDCk6/zQiHw759b9Dv9IRDYSkkz7w/6Ld93JA5ku23XRy/KkVZWZnuv/9+jR07VuPHj9fixYt15syZ+FUqZs6cqf79+2vBggWSpEcffVS33HKLFi5cqGnTpmnNmjXatm2bXnvtNSdfBgAAADzO8WB877336vjx45o3b56qq6s1evRobdiwIX6C3YEDBxS+aAzMzTffrNWrV+vZZ5/VM888o4KCAr3zzjspXcMYAAAAOM/xYCxJpaWlCYdObNq06bLH7rnnHt1zzz0ZrgoAAABB4v5R+AAAAIANXLHH2E6xWEySUr7RRzQa1ekzp+PzsPLWhZmct98lWnbn++zGfnuxDrfwe7/N8GLNZtHv1HnxNXbU76NHj6Y835rj31zV6tChQ55YFpnkluWRqI7zfTYMw9Z6QrHzSTEgDh06ZOlNPgAAAJAZlZWVGjdunG1/L3DB2DAMHTlyRF26dGl10fOTJ09q4MCBOnjwoPLy8hysMHnU3LFoNKq9e/dq6NChrbaGWXb2oN+po+aO0W9n2V1zc3OzKioqNGLECGVlXTjgferUKd14442qqqpSly5dMl6HFai5Y4ZhqKamRoWFha36nWmBG0oRDoc1YMCAhL/Py8vzzErpPGpuX3tbmiw7e9Dv1FFz++i38+ys+c4777zssfNDLPr37++ZZUfNyRk0aJAtf+dinHwHAAAAiGAMAAAASCIYx2VnZ2v+/PnKzs52upSkUbP36zCDmr1fhxnU7P06zKBm79dhBjW7V+BOvgMAAADawh5jAAAAQARjAAAAQBLBGAAAAJBEMAYAAAAkEYwlSUuWLNGQIUOUk5OjCRMmqLKy0umS2rVgwQKNGzdOXbp0Ue/evTV9+nTt3r3b6bKS9pvf/EahUEiPPfaYI3+fftuLfptDv9NDv+1Fv82h3+4X+GC8du1alZWVaf78+frkk080atQolZSU6NixY06XltAHH3ygOXPm6OOPP9bGjRvV3NysKVOm6MyZM06X1qGtW7dq2bJluummmxz5+/TbXvTbPPqdOvptL/ptHv32gFjAjR8/PjZnzpz4/6PRaKxfv36xBQsWOFiVOceOHYtJin3wwQdOl9KuU6dOxQoKCmIbN26M3XLLLbFHH33U9hrot33otzXod/Lot33otzXot/sEeo9xU1OTtm/fruLi4vhj4XBYxcXFqqiocLAyc+rr6yVJ3bt3d7iS9s2ZM0fTpk1rtbztRL/tRb+tQb+TQ7/tRb+tQb/dJ8vpApxUW1uraDSq/Pz8Vo/n5+dr165dDlVljmEYeuyxx/Sd73xHI0aMcLqchNasWaNPPvlEW7dudawG+m0f+m0N+p08+m0f+m0N+u1OgQ7GfjBnzhx9/vnn+uijj5wuJaGDBw/q0Ucf1caNG5WTk+N0OZ5Gv4OFfgcL/Q4W+u1OgQ7GPXv2VCQSUU1NTavHa2pq1KdPH4eqSl5paaneffddbd68WQMGDHC6nIS2b9+uY8eOacyYMfHHotGoNm/erFdeeUWNjY2KRCIZr4N+24N+W4N+m0O/7UG/rUG/3SvQY4w7d+6soqIilZeXxx8zDEPl5eWaNGmSg5W1LxaLqbS0VG+//bbef/99XXPNNU6X1K7bbrtNn332mXbs2BH/GTt2rGbMmKEdO3bY9qGi3/ag3+mh36mh3/ag3+mh3x7g6Kl/LrBmzZpYdnZ2bOXKlbGqqqrYT37yk9jVV18dq66udrq0hB555JFY165dY5s2bYodPXo0/nP27FmnS0uaU2e10m9n0O/k0e/U0W9n0O/k0W/3C3wwjsVisd/97nexQYMGxTp37hwbP3587OOPP3a6pHZJavNnxYoVTpeWNCc/WPTbfvQ7efQ7PfTbfvQ7efTb/UKxWCxmx55pAAAAwM0CPcYYAAAAOI9gDAAAAIhgDAAAAEgiGAMAAACSCMYAAACAJIIxAAAAIIlgDAAAAEgiGAMAAACSCMa+9NVXX6l379763//+1+50tbW16t27tw4dOmRPYcgI+h0s9DtY6Hew0G/ncec7HyorK9OpU6f0+uuvdzjtE088obq6Ov3xj3+0oTJkAv0OFvodLPQ7WOi38wjGPnP27Fn17dtX//znPzVx4sQOp9+5c6eKiop05MgRde/e3YYKYSX6HSz0O1jod7DQb3dgKIXPvPfee8rOzo5/qOrq6jRjxgz16tVLubm5Kigo0IoVK+LTDx8+XP369dPbb7/tVMlIA/0OFvodLPQ7WOi3O2Q5XQCs9eGHH6qoqCj+/+eee05VVVX6xz/+oZ49e2rv3r1qaGho9Zzx48frww8/1IMPPmh3uUgT/Q4W+h0s9DtY6Lc7EIx9Zv/+/erXr1/8/wcOHFBhYaHGjh0rSRoyZMhlz+nXr58+/fRTu0qEheh3sNDvYKHfwUK/3YGhFD7T0NCgnJyc+P8feeQRrVmzRqNHj9ZTTz2lLVu2XPac3NxcnT171s4yYRH6HSz0O1jod7DQb3cgGPtMz549VVdXF///7bffrv379+vxxx/XkSNHdNttt+mJJ55o9ZwTJ06oV69edpcKC9DvYKHfwUK/g4V+uwPB2GcKCwtVVVXV6rFevXrp/vvv16pVq7R48WK99tprrX7/+eefq7Cw0M4yYRH6HSz0O1jod7DQb3cgGPtMSUmJdu7cGd/qnDdvntatW6e9e/dq586devfddzVs2LD49GfPntX27ds1ZcoUp0pGGuh3sNDvYKHfwUK/3YFg7DMjR47UmDFj9Oabb0qSOnfurLlz5+qmm27S9773PUUiEa1ZsyY+/bp16zRo0CB997vfdapkpIF+Bwv9Dhb6HSz02yVi8J133303NmzYsFg0Gu1w2gkTJsTeeOMNG6pCptDvYKHfwUK/g4V+O4/LtfnQtGnTtGfPHh0+fFgDBw5MOF1tba3uuusu3XfffTZWB6vR72Ch38FCv4OFfjuPW0IDAAAAYowxAAAAIIlgDAAAAEgiGAMAAACSCMYAAACAJIIxAAAAIIlgDAAAAEgiGAMAAACSCMYAAACAJIIxAAAAIEn6f2Y501r2ad4eAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 2: Vary the duration AND the high level\n", + "\n", + "# It is straighforward to vary several things throughout the sequence\n", + "\n", + "# We make the same base element as in Example 1\n", + "basebp = bb.BluePrint()\n", + "basebp.insertSegment(0, ramp, (0, 0), dur=0.5)\n", + "basebp.insertSegment(1, ramp, (1, 1), dur=1, name=\"varyme\")\n", + "basebp.insertSegment(2, \"waituntil\", 5)\n", + "basebp.setSR(100)\n", + "\n", + "baseelem = bb.Element()\n", + "baseelem.addBluePrint(1, basebp)\n", + "\n", + "# Now we make a 5-step sequence varying the duration AND the high level\n", + "# We thus vary 3 things, a duration, a ramp start, and a ramp stop\n", + "channels = [1, 1, 1]\n", + "names = [\"varyme\", \"varyme\", \"varyme\"]\n", + "args = [\"duration\", \"start\", \"stop\"]\n", + "iters = [[1, 1.5, 2, 2.5, 3], [1, 0.8, 0.7, 0.6, 0.5], [1, 0.8, 0.7, 0.6, 0.5]]\n", + "\n", + "seq2 = bb.makeVaryingSequence(baseelem, channels, names, args, iters)\n", + "plotter(seq2)" + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:23.506807Z", + "iopub.status.busy": "2025-01-21T06:30:23.506479Z", + "iopub.status.idle": "2025-01-21T06:30:23.907935Z", + "shell.execute_reply": "2025-01-21T06:30:23.907363Z" + } + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/opt/hostedtoolcache/Python/3.11.11/x64/lib/python3.11/site-packages/broadbean/sequence.py:190: UserWarning: Deprecation warning. This function is only compatible with AWG5014 output and will be removed. Please use the specific setSequencingXXX methods.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "image/png": "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/ywMRiN/odEwJsQ0UTxmJk/3UorNmzfjhhtuwLJly7B8+XJs374dkUgEN954IwBgw4YNmD59OrZu3QoA+OpXv4qLLroI27Ztw+WXX47f/OY32LdvHx5++GE9PwYRERERFTndE+Nrr70Wvb29uPfee9HV1YVFixbhhRdeGL7Brq2tTTEjef755+OJJ57A3XffjW9961uYPXs2nn766Zz1MCYiIiKi8qB7YgwAmzZtGrV04uWXX9aMrV+/HuvXr89xVERERERUTliEQkRERESEApkxzqehhf640Ed2iKKIcCQM4PTPdCKr2lB54jGT2VR+LkPns87OzpzERkQ0GaIoorv3dCexkydPTui8NnQ+k/Lc2rPsEuNQKAQA7GVMRCVn+fLleodARJRV3d3dmDFjRt72J8hDU6hlQpIkdHR0wO12T6gd0tDCIO3t7fB4PDmMkEoFjxmaqMkeM6lUCnv27MH8+fNhMo1/viMUCmHevHk4cOAA3G73ZEKmMsNjhiZqsseMJEno7u7G4sWLJ3Rem6qyS4wnKxgMwuv1IhAIMMmhceExQxOV72OGxyhNFI8ZmqhiO2Z48x0REREREZgYExEREREBYGI8blarFffddx+sVqveoVCR4DFDE5XvY4bHKE0UjxmaqGI7ZlhjTEREREQEzhgTEREREQFgYkxEREREBICJMRERERERACbGREREREQAmBiP20MPPYSWlhbYbDasWLECe/fu1TskKlC7d+/GFVdcgWnTpkEQBDz99NN6h0QFbuvWrTjvvPPgdrtRW1uLK6+8EocPH875fnleo/HasmULBEFQ/DnnnHP0DosKyFi/+2RZxr333ouGhgbY7XasWbMGR44c0SfYM2BiPA5PPvkkNm/ejPvuuw9vvvkmFi5ciHXr1qGnp0fv0KgARSIRLFy4EA899JDeoVCR+Otf/4rbbrsNr7/+Onbu3IlUKoW1a9ciEonkbJ88r9FEnXvuuejs7Bz+8+qrr+odEhWQsX73ff/738e//du/YceOHXjjjTfgdDqxbt06xOPxPEd6ZmzXNg4rVqzAeeedhwcffBDA6fW7m5qa8OUvfxl33nmnztFRIRMEAb///e9x5ZVX6h0KFZHe3l7U1tbir3/9Ky688MKc7IPnNZqILVu24Omnn0Zra6veoVARUP/uk2UZ06ZNw9e//nV84xvfAAAEAgHU1dXhsccewxe+8AUdo1XijPEYkskk9u/fjzVr1gyPGQwGrFmzBnv27NExMiIqVYFAAABQWVmZk+3zvEaTceTIEUybNg2zZs3C9ddfj7a2Nr1DoiJx7NgxdHV1Kc45Xq8XK1asKLhzDhPjMfT19UEURdTV1SnG6+rq0NXVpVNURFSqJEnC7bffjgsuuADz58/PyT54XqOJWrFiBR577DG88MIL+NnPfoZjx47h05/+NEKhkN6hUREYOq8UwznHpHcARET0sdtuuw3vvfce6zepoFx66aXD///JT34SK1asQHNzM5566incfPPNOkZGlF2cMR5DdXU1jEYjuru7FePd3d2or6/XKSoiKkWbNm3Cs88+i5deegmNjY052w/PazRVFRUVOPvss/Hhhx/qHQoVgaHzSjGcc5gYj8FisWDp0qXYtWvX8JgkSdi1axdWrVqlY2REVCpkWcamTZvw+9//Hn/5y18wc+bMnO6P5zWaqnA4jI8++ggNDQ16h0JFYObMmaivr1ecc4LBIN54442CO+ewlGIcNm/ejBtuuAHLli3D8uXLsX37dkQiEdx44416h0YFKBwOK2ZRjh07htbWVlRWVmLGjBk6RkaF6rbbbsMTTzyBZ555Bm63e7jmzuv1wm6352SfPK/RRHzjG9/AFVdcgebmZnR0dOC+++6D0WjEddddp3doVCDG+t13++2343vf+x5mz56NmTNn4p577sG0adMKr2uTTOPyk5/8RJ4xY4ZssVjk5cuXy6+//rreIVGBeumll2QAmj833HCD3qFRgcp0vACQH3300Zzul+c1Gq9rr71WbmhokC0Wizx9+nT52muvlT/88EO9w6ICMtbvPkmS5HvuuUeuq6uTrVarfMkll8iHDx/WN+gM2MeYiIiIiAisMSYiIiIiAsDEmIiIiIgIABNjIiIiIiIATIyJiIiIiAAwMSYiIiIiAsDEmIiIiIgIABNjIiIiIiIATIyJiIiIiAAwMSYat/7+ftTW1uL48eM52f6dd96JL3/5yznZNhFRJjyvESlx5Tuicdq8eTNCoRB+8Ytf5GT7fX19mDVrFlpbWzFr1qyc7IOIaCSe14iUmBgTjUM0GkVDQwNefPFFrFy5Mmf7Wb9+PVpaWvCDH/wgZ/sgIgJ4XiPKhKUUROPw3HPPwWq1Dv/yePnllyEIAl588UUsXrwYdrsdq1evRk9PD55//nnMnTsXHo8HX/ziFxGNRoe389vf/hYLFiyA3W5HVVUV1qxZg0gkMvz8FVdcgd/85jd5/3xEVH54XiPSMukdAFExeOWVV7B06VLN+JYtW/Dggw/C4XDgmmuuwTXXXAOr1YonnngC4XAYV111FX7yk5/gm9/8Jjo7O3Hdddfh+9//Pq666iqEQiG88sorGHnRZvny5Th58iSOHz+OlpaWPH5CIio3PK8RaTExJhqHEydOYNq0aZrx733ve7jgggsAADfffDPuuusufPTRR8O1dFdffTVeeuml4V8g6XQan//859Hc3AwAWLBggWJ7Q/s4ceIEf4EQUU7xvEakxVIKonGIxWKw2Wya8U9+8pPD/19XVweHw6G4waSurg49PT0AgIULF+KSSy7BggULsH79evziF7/A4OCgYnt2ux0AFJcpiYhygec1Ii0mxkTjUF1drTnZA4DZbB7+f0EQFI+HxiRJAgAYjUbs3LkTzz//PObNm4ef/OQnmDNnDo4dOzb8+oGBAQBATU1NLj4GEdEwnteItJgYE43D4sWLceDAgSlvRxAEXHDBBfjOd76Dt956CxaLBb///e+Hn3/vvfdgNptx7rnnTnlfRERnwvMakRYTY6JxWLduHd5///2Msyvj9cYbb+D+++/Hvn370NbWht/97nfo7e3F3Llzh1/zyiuv4NOf/vTwpUciolzheY1Ii4kx0TgsWLAAS5YswVNPPTXpbXg8HuzevRuXXXYZzj77bNx9993Ytm0bLr300uHX/OY3v8HGjRuzETIR0RnxvEakxQU+iMbpj3/8I+644w689957MBiy/53y+eefx9e//nW88847MJnYMIaIco/nNSIlHqVE43T55ZfjyJEjOHXqFJqamrK+/UgkgkcffZS/PIgob3heI1LijDEREREREVhjTEREREQEgIkxEREREREAJsZERERERACYGBMRERERAWBiTEREREQEgIkxEREREREAJsZERERERACYGBMRERERAWBiTEREREQEAPj/tL1yA0I5hP8AAAAASUVORK5CYII=", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAsYAAAEwCAYAAAC0S7csAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAX25JREFUeJztnXmUXMV977+3by+zaGYkIWlmtEuWALFYSAIE8gLGCgJjHJJjzEnwwTEGLwc9x1Ge88ALOLFj+dhhSWJieQlwbMcxjuMlz2B4WEbIxMJCgILNDhJakEb77DO93Fvvj9E001XV09td6nZ/P+fMQXO7+/ZvqPur+tVvK0sIIUAIIYQQQkiDEwtbAEIIIYQQQkyAhjEhhBBCCCGgYUwIIYQQQggAGsaEEEIIIYQAoGFMCCGEEEIIABrGhBBCCCGEAKBhTAghhBBCCAAaxoQQQgghhACgYUwIIYQQQggAGsaEEEIIIYQAoGEcOTZv3ozrr78ep556KlpaWrB48WLccMMNOHjwoPb9v/3tb/H2t78dLS0t6Orqwic/+UkMDg4GLHXjcPz4cQwPD2tfO3jwIG6++Wa8613vQltbGyzLwpYtW4IVsEGh3pjNZHqzdetWvO9978O8efPQ1NSErq4uXHbZZfjv//7vgKVsTKg7ZjOZ7tx3332wLEv709PTE7Ck0cESQoiwhWhEfvzjHxc8mC0tLfiLv/gLxGKT71XOPfdcHD9+HFdffTWWLl2KXbt24etf/zpaWlqwc+dOdHV15d+7c+dOXHjhhVi2bBk++tGPYv/+/fiHf/gHvOtd78Ivf/lL3/62Rubiiy+Gbdv4xS9+gebm5oLXtmzZgne9611YunQpZsyYgW3btuHRRx/FxRdfHI6wEYR6U59Mpjff+c538Itf/ALnnXceurq6cOLECXz/+9/H73//ezzwwAO47LLLQpI6WlB36pPJdOe+++7Dhz/8Yfzd3/0dFi1aVPDa+9//fjQ1NQUpanQQJBRWr14tABT8PPbYYyU/99hjjwnHcZRrAMRnP/vZguuXX3656O7uFn19fflr3/72twUA8fDDD3vzh5ACtm7dKlpbW8Uf/dEfiZGRkYLX+vv7xbFjx4QQQvzHf/yHACAeffTREKSMLtSb+mQyvdExNDQkOjs7xbp16wKQrj6g7tQnk+nOvffeKwCIJ598MiTpoglTKULiiSeegBACf/mXf5m/5rpuwXv+5V/+BZlMpuDaO9/5TmWH/853vhPTp0/HCy+8kL/W39+PRx55BB/84AfR3t6ev37ddddhypQp+NGPfuTln9Ow7NixA5s2bcr/PPfcc7jyyivxyCOP4E//9E8Lxq+trQ3Tp08PUdroQ72pDyrRGx0tLS2YOXMment7gxG4DqDu1AfV6s7AwAAcxwlY2ogSsmHe0PzqV78SAMT//t//W/Ee7tmzR7S0tIjbbrut5H0GBgZEMpkUH/3oR/PXHn/8cQFA3H///cr73/72t4uVK1d68Sc0PLfddpvihRn/sW1b7NmzR/s5eoyrh3oTfarRm76+PnHkyBHxwgsviFtuuUUAEJ/5zGdCkD66UHeiTyW6M+4xnjJligAgksmkuPLKK8XLL78c4l9gPvQYh8i3vvUtrFq1CjfddJPy2vz583HTTTfhW9/6FkSJNPC77roLmUwG11xzTf7aeGFEd3e38v7u7m4cOHCgRunJOAsWLIAQAkIIuK6LG264AbFYDN/97ncxf/78sMWrO6g39UGlevOBD3wAM2fOxLJly3D77bfjYx/7GD7/+c+HIHl0oe7UB+Xqznge+d13342f/vSn+Ju/+Rts3rwZa9aswb59+0L8C8yGhnGI/P73v8cll1yC5uZmvPvd78a0adMKXr/44otx8OBBHD16tOg9tm7dir/927/FBz7wAVxyySX56yMjIwCAVCqlfKapqSn/OvGW9evX45577sG9996LP//zPw9bnLqEelN/lKM3X/nKV/D//t//w7/+67/iggsuQCaTQS6XC1jSaEPdqT8m050PfOADuPfee3Hdddfhqquuwhe/+EU8/PDDOHbsGP7+7/8+JInNJx62AI1MIpFAOp1GZ2cnfvWrXymvp9NpAIBt29rPv/jii/iTP/kTnHXWWfjOd75T8Np4der4PSYyOjqqVK8Sb5g3bx6+9a1v4brrrgtblLqFelN/lKM355xzTv7fH/zgB7Fy5Ur8xV/8BX784x8HIGF9QN2pPypdc97+9rdj9erV2vEnY9BjHCIXXHABHnjggaJej5///OdYvHixtmBr3759uPTSS9HR0YEHH3wQbW1tBa+Ph7N0vSYPHjyI2bNne/AXEJmbb74ZH/nIR8IWo66h3tQflepNMpnE+973PvzkJz+hJ7ICqDv1RzVrzrx5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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Example 3: Modify the high level of a square pulse inside a sequence\n", + "\n", + "#\n", + "\n", + "pulsebp = bb.BluePrint()\n", + "pulsebp.insertSegment(0, ramp, (0, 0), dur=5e-4)\n", + "pulsebp.insertSegment(1, ramp, (1, 1), dur=1e-3, name=\"varyme\")\n", + "pulsebp.insertSegment(2, \"waituntil\", 2e-3)\n", + "pulsebp.setSR(1e6)\n", + "\n", + "sinebp = bb.BluePrint()\n", + "sinebp.insertSegment(0, sine, (0.2e3, 0.5, 0.5, 0), dur=10e-3)\n", + "sinebp.setSR(1e6)\n", + "\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, pulsebp)\n", + "\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(1, sinebp)\n", + "\n", + "baseseq = bb.Sequence()\n", + "baseseq.setSR(1e6)\n", + "baseseq.addElement(1, elem1)\n", + "baseseq.addElement(2, elem2)\n", + "\n", + "baseseq.setSequenceSettings(1, 0, 20, 0, 0)\n", + "baseseq.setSequenceSettings(2, 0, 1, 0, 1)\n", + "\n", + "plotter(baseseq)\n", + "\n", + "# now vary this sequence\n", + "\n", + "poss = [1, 1]\n", + "channels = [1, 1]\n", + "names = [\"varyme\", \"varyme\"]\n", + "args = [\"start\", \"stop\"]\n", + "iters = [[1, 0.75, 0.5], [1, 0.75, 0.5]]\n", + "\n", + "newseq = bb.repeatAndVarySequence(baseseq, poss, channels, names, args, iters)\n", + "plotter(newseq)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/Subsequences.html b/examples/Subsequences.html new file mode 100644 index 000000000..344867f2d --- /dev/null +++ b/examples/Subsequences.html @@ -0,0 +1,4581 @@ + + + + + + + + + Subsequences - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Subsequences

+

This notebook describes the use of subsequences.

+

Subsequences can be useful in a wide range of settings.

+
+
[1]:
+
+
+
%matplotlib notebook
+
+import broadbean as bb
+from broadbean.plotting import plotter
+
+sine = bb.PulseAtoms.sine
+ramp = bb.PulseAtoms.ramp
+
+
+
+
+

Example 1: Compression

+

In a waveform with very long “dead” periods, subsequences can – via the option to repeat elements – drastically reduce the number of points of the entire sequence.

+

Here we imagine a pulse sequence where we first wait, then perturb the system, then wait some more for readout. We’d like to vary the height of the perturbation.

+
+

Uncompressed

+
+
[2]:
+
+
+
# Uncompressed
+
+SR = 1e9
+t1 = 200e-6  # wait
+t2 = 20e-9  # perturb the system
+t3 = 250e-6  # read out
+
+bp1 = bb.BluePrint()
+bp1.insertSegment(0, ramp, (0, 0), dur=t1)
+bp1.insertSegment(1, ramp, (1, 1), dur=t2, name="perturbation")
+bp1.insertSegment(2, ramp, (0, 0), dur=t3)
+bp1.setSR(SR)
+
+
+
+
+
[3]:
+
+
+
plotter(bp1)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[4]:
+
+
+
# Now make a variation of the height
+
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp1)
+
+elem2 = elem1.copy()
+elem2.changeArg(1, "perturbation", "start", 0.75)
+elem2.changeArg(1, "perturbation", "stop", 0.75)
+
+elem3 = elem1.copy()
+elem3.changeArg(1, "perturbation", "start", 0.5)
+elem3.changeArg(1, "perturbation", "stop", 0.5)
+
+# And put that together in a sequence
+seq = bb.Sequence()
+seq.addElement(1, elem1)
+seq.addElement(2, elem2)
+seq.addElement(3, elem3)
+seq.setSR(SR)
+
+
+
+
+
[5]:
+
+
+
plotter(seq)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[6]:
+
+
+
# The sequence is long and heavy on the memory
+seq.points
+
+
+
+
+
[6]:
+
+
+
+
+1350060
+
+
+
+
+

Compressed

+
+
[7]:
+
+
+
# Let's make a sequence instead of an element
+
+SR = 1e9
+t1 = 200e-6  # wait
+t2 = 20e-9  # perturb the system
+t3 = 250e-6  # read out
+
+compression = 100  # this number has to be chosen with some care
+
+bp1 = bb.BluePrint()
+bp1.insertSegment(0, ramp, (0, 0), dur=t1 / compression)
+bp1.setSR(SR)
+elem1 = bb.Element()
+elem1.addBluePrint(1, bp1)
+#
+bp2 = bb.BluePrint()
+bp2.insertSegment(0, ramp, (1, 1), dur=t2, name="perturbation")
+bp2.setSR(SR)
+elem2 = bb.Element()
+elem2.addBluePrint(1, bp2)
+#
+bp3 = bb.BluePrint()
+bp3.insertSegment(0, ramp, (0, 0), dur=t3 / compression)
+bp3.setSR(SR)
+elem3 = bb.Element()
+elem3.addBluePrint(1, bp3)
+
+seq = bb.Sequence()
+seq.addElement(1, elem1)
+seq.setSequencingNumberOfRepetitions(1, compression)
+seq.addElement(2, elem2)
+seq.addElement(3, elem3)
+seq.setSequencingNumberOfRepetitions(3, compression)
+seq.setSR(SR)
+
+# Now make the variation
+seq2 = seq.copy()
+seq2.element(2).changeArg(1, "perturbation", "start", 0.75)
+seq2.element(2).changeArg(1, "perturbation", "stop", 0.75)
+#
+seq3 = seq.copy()
+seq3.element(2).changeArg(1, "perturbation", "start", 0.5)
+seq3.element(2).changeArg(1, "perturbation", "stop", 0.5)
+#
+fullseq = seq + seq2 + seq3
+plotter(fullseq)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[8]:
+
+
+
# The above sequence achieves the same as the uncompresed, but has fewer points
+fullseq.points
+
+
+
+
+
[8]:
+
+
+
+
+13560
+
+
+
+
+

Now using subsequences

+

Subsequences come into play when we want to, say, repeat each wait-perturb-wait element 25 times. In the uncompressed case, that can only be achieved by adding each element 24 times more, thus resulting in a very large output file. Using subsequences, we can get away with a much smaller file size.

+
+
[9]:
+
+
+
mainseq = bb.Sequence()
+mainseq.setSR(SR)
+
+mainseq.addSubSequence(1, seq)
+mainseq.addSubSequence(2, seq2)
+mainseq.addSubSequence(3, seq3)
+
+mainseq.setSequencingNumberOfRepetitions(1, 25)
+mainseq.setSequencingNumberOfRepetitions(2, 25)
+mainseq.setSequencingNumberOfRepetitions(3, 25)
+
+plotter(mainseq)
+
+# The plotting does not show the details of the subsequence,
+# but it DOES show the min and max voltages of a subsequence
+# as grey lines
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
[10]:
+
+
+
# The number of points is still low
+mainseq.points
+
+
+
+
+
[10]:
+
+
+
+
+13560
+
+
+
+
[ ]:
+
+
+

+
+
+
+
+
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/examples/Subsequences.ipynb b/examples/Subsequences.ipynb new file mode 100644 index 000000000..d1d369488 --- /dev/null +++ b/examples/Subsequences.ipynb @@ -0,0 +1,4401 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Subsequences\n", + "\n", + "This notebook describes the use of subsequences.\n", + "\n", + "Subsequences can be useful in a wide range of settings." + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:25.515301Z", + "iopub.status.busy": "2025-01-21T06:30:25.515134Z", + "iopub.status.idle": "2025-01-21T06:30:25.891915Z", + "shell.execute_reply": "2025-01-21T06:30:25.891368Z" + } + }, + "outputs": [], + "source": [ + "%matplotlib notebook\n", + "\n", + "import broadbean as bb\n", + "from broadbean.plotting import plotter\n", + "\n", + "sine = bb.PulseAtoms.sine\n", + "ramp = bb.PulseAtoms.ramp" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Example 1: Compression\n", + "\n", + "In a waveform with very long \"dead\" periods, subsequences can -- via the option to repeat elements -- drastically reduce the number of points of the entire sequence.\n", + "\n", + "Here we imagine a pulse sequence where we first wait, then perturb the system, then wait some more for readout. We'd like to vary the height of the perturbation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Uncompressed" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:25.894077Z", + "iopub.status.busy": "2025-01-21T06:30:25.893753Z", + "iopub.status.idle": "2025-01-21T06:30:25.897785Z", + "shell.execute_reply": "2025-01-21T06:30:25.897329Z" + } + }, + "outputs": [], + "source": [ + "# Uncompressed\n", + "\n", + "SR = 1e9\n", + "t1 = 200e-6 # wait\n", + "t2 = 20e-9 # perturb the system\n", + "t3 = 250e-6 # read out\n", + "\n", + "bp1 = bb.BluePrint()\n", + "bp1.insertSegment(0, ramp, (0, 0), dur=t1)\n", + "bp1.insertSegment(1, ramp, (1, 1), dur=t2, name=\"perturbation\")\n", + "bp1.insertSegment(2, ramp, (0, 0), dur=t3)\n", + "bp1.setSR(SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:25.899420Z", + "iopub.status.busy": "2025-01-21T06:30:25.899071Z", + "iopub.status.idle": "2025-01-21T06:30:26.111595Z", + "shell.execute_reply": "2025-01-21T06:30:26.110983Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotter(bp1)" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:26.113565Z", + "iopub.status.busy": "2025-01-21T06:30:26.113203Z", + "iopub.status.idle": "2025-01-21T06:30:26.118401Z", + "shell.execute_reply": "2025-01-21T06:30:26.117854Z" + } + }, + "outputs": [], + "source": [ + "# Now make a variation of the height\n", + "\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp1)\n", + "\n", + "elem2 = elem1.copy()\n", + "elem2.changeArg(1, \"perturbation\", \"start\", 0.75)\n", + "elem2.changeArg(1, \"perturbation\", \"stop\", 0.75)\n", + "\n", + "elem3 = elem1.copy()\n", + "elem3.changeArg(1, \"perturbation\", \"start\", 0.5)\n", + "elem3.changeArg(1, \"perturbation\", \"stop\", 0.5)\n", + "\n", + "# And put that together in a sequence\n", + "seq = bb.Sequence()\n", + "seq.addElement(1, elem1)\n", + "seq.addElement(2, elem2)\n", + "seq.addElement(3, elem3)\n", + "seq.setSR(SR)" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:26.119971Z", + "iopub.status.busy": "2025-01-21T06:30:26.119663Z", + "iopub.status.idle": "2025-01-21T06:30:26.697560Z", + "shell.execute_reply": "2025-01-21T06:30:26.696959Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "plotter(seq)" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:26.699538Z", + "iopub.status.busy": "2025-01-21T06:30:26.699240Z", + "iopub.status.idle": "2025-01-21T06:30:26.703574Z", + "shell.execute_reply": "2025-01-21T06:30:26.703103Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "1350060" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The sequence is long and heavy on the memory\n", + "seq.points" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Compressed" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:26.705325Z", + "iopub.status.busy": "2025-01-21T06:30:26.705025Z", + "iopub.status.idle": "2025-01-21T06:30:26.811937Z", + "shell.execute_reply": "2025-01-21T06:30:26.811355Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Let's make a sequence instead of an element\n", + "\n", + "SR = 1e9\n", + "t1 = 200e-6 # wait\n", + "t2 = 20e-9 # perturb the system\n", + "t3 = 250e-6 # read out\n", + "\n", + "compression = 100 # this number has to be chosen with some care\n", + "\n", + "bp1 = bb.BluePrint()\n", + "bp1.insertSegment(0, ramp, (0, 0), dur=t1 / compression)\n", + "bp1.setSR(SR)\n", + "elem1 = bb.Element()\n", + "elem1.addBluePrint(1, bp1)\n", + "#\n", + "bp2 = bb.BluePrint()\n", + "bp2.insertSegment(0, ramp, (1, 1), dur=t2, name=\"perturbation\")\n", + "bp2.setSR(SR)\n", + "elem2 = bb.Element()\n", + "elem2.addBluePrint(1, bp2)\n", + "#\n", + "bp3 = bb.BluePrint()\n", + "bp3.insertSegment(0, ramp, (0, 0), dur=t3 / compression)\n", + "bp3.setSR(SR)\n", + "elem3 = bb.Element()\n", + "elem3.addBluePrint(1, bp3)\n", + "\n", + "seq = bb.Sequence()\n", + "seq.addElement(1, elem1)\n", + "seq.setSequencingNumberOfRepetitions(1, compression)\n", + "seq.addElement(2, elem2)\n", + "seq.addElement(3, elem3)\n", + "seq.setSequencingNumberOfRepetitions(3, compression)\n", + "seq.setSR(SR)\n", + "\n", + "# Now make the variation\n", + "seq2 = seq.copy()\n", + "seq2.element(2).changeArg(1, \"perturbation\", \"start\", 0.75)\n", + "seq2.element(2).changeArg(1, \"perturbation\", \"stop\", 0.75)\n", + "#\n", + "seq3 = seq.copy()\n", + "seq3.element(2).changeArg(1, \"perturbation\", \"start\", 0.5)\n", + "seq3.element(2).changeArg(1, \"perturbation\", \"stop\", 0.5)\n", + "#\n", + "fullseq = seq + seq2 + seq3\n", + "plotter(fullseq)" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:26.813675Z", + "iopub.status.busy": "2025-01-21T06:30:26.813499Z", + "iopub.status.idle": "2025-01-21T06:30:26.818079Z", + "shell.execute_reply": "2025-01-21T06:30:26.817593Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "13560" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The above sequence achieves the same as the uncompresed, but has fewer points\n", + "fullseq.points" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Now using subsequences\n", + "\n", + "Subsequences come into play when we want to, say, repeat each wait-perturb-wait element 25 times.\n", + "In the uncompressed case, that can only be achieved by adding each element 24 times more, thus resulting in a very large output file. Using subsequences, we can get away with a much smaller file size." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:26.819817Z", + "iopub.status.busy": "2025-01-21T06:30:26.819427Z", + "iopub.status.idle": "2025-01-21T06:30:26.860253Z", + "shell.execute_reply": "2025-01-21T06:30:26.859647Z" + } + }, + "outputs": [ + { + "data": { + "application/javascript": [ + "/* Put everything inside the global mpl namespace */\n", + "/* global mpl */\n", + "window.mpl = {};\n", + "\n", + "mpl.get_websocket_type = function () {\n", + " if (typeof WebSocket !== 'undefined') {\n", + " return WebSocket;\n", + " } else if (typeof MozWebSocket !== 'undefined') {\n", + " return MozWebSocket;\n", + " } else {\n", + " alert(\n", + " 'Your browser does not have WebSocket support. ' +\n", + " 'Please try Chrome, Safari or Firefox ≥ 6. ' +\n", + " 'Firefox 4 and 5 are also supported but you ' +\n", + " 'have to enable WebSockets in about:config.'\n", + " );\n", + " }\n", + "};\n", + "\n", + "mpl.figure = function (figure_id, websocket, ondownload, parent_element) {\n", + " this.id = figure_id;\n", + "\n", + " this.ws = websocket;\n", + "\n", + " this.supports_binary = this.ws.binaryType !== undefined;\n", + "\n", + " if (!this.supports_binary) {\n", + " var warnings = document.getElementById('mpl-warnings');\n", + " if (warnings) {\n", + " warnings.style.display = 'block';\n", + " warnings.textContent =\n", + " 'This browser does not support binary websocket messages. ' +\n", + " 'Performance may be slow.';\n", + " }\n", + " }\n", + "\n", + " this.imageObj = new Image();\n", + "\n", + " this.context = undefined;\n", + " this.message = undefined;\n", + " this.canvas = undefined;\n", + " this.rubberband_canvas = undefined;\n", + " this.rubberband_context = undefined;\n", + " this.format_dropdown = undefined;\n", + "\n", + " this.image_mode = 'full';\n", + "\n", + " this.root = document.createElement('div');\n", + " this.root.setAttribute('style', 'display: inline-block');\n", + " this._root_extra_style(this.root);\n", + "\n", + " parent_element.appendChild(this.root);\n", + "\n", + " this._init_header(this);\n", + " this._init_canvas(this);\n", + " this._init_toolbar(this);\n", + "\n", + " var fig = this;\n", + "\n", + " this.waiting = false;\n", + "\n", + " this.ws.onopen = function () {\n", + " fig.send_message('supports_binary', { value: fig.supports_binary });\n", + " fig.send_message('send_image_mode', {});\n", + " if (fig.ratio !== 1) {\n", + " fig.send_message('set_device_pixel_ratio', {\n", + " device_pixel_ratio: fig.ratio,\n", + " });\n", + " }\n", + " fig.send_message('refresh', {});\n", + " };\n", + "\n", + " this.imageObj.onload = function () {\n", + " if (fig.image_mode === 'full') {\n", + " // Full images could contain transparency (where diff images\n", + " // almost always do), so we need to clear the canvas so that\n", + " // there is no ghosting.\n", + " fig.context.clearRect(0, 0, fig.canvas.width, fig.canvas.height);\n", + " }\n", + " fig.context.drawImage(fig.imageObj, 0, 0);\n", + " };\n", + "\n", + " this.imageObj.onunload = function () {\n", + " fig.ws.close();\n", + " };\n", + "\n", + " this.ws.onmessage = this._make_on_message_function(this);\n", + "\n", + " this.ondownload = ondownload;\n", + "};\n", + "\n", + "mpl.figure.prototype._init_header = function () {\n", + " var titlebar = document.createElement('div');\n", + " titlebar.classList =\n", + " 'ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix';\n", + " var titletext = document.createElement('div');\n", + " titletext.classList = 'ui-dialog-title';\n", + " titletext.setAttribute(\n", + " 'style',\n", + " 'width: 100%; text-align: center; padding: 3px;'\n", + " );\n", + " titlebar.appendChild(titletext);\n", + " this.root.appendChild(titlebar);\n", + " this.header = titletext;\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (_canvas_div) {};\n", + "\n", + "mpl.figure.prototype._init_canvas = function () {\n", + " var fig = this;\n", + "\n", + " var canvas_div = (this.canvas_div = document.createElement('div'));\n", + " canvas_div.setAttribute('tabindex', '0');\n", + " canvas_div.setAttribute(\n", + " 'style',\n", + " 'border: 1px solid #ddd;' +\n", + " 'box-sizing: content-box;' +\n", + " 'clear: both;' +\n", + " 'min-height: 1px;' +\n", + " 'min-width: 1px;' +\n", + " 'outline: 0;' +\n", + " 'overflow: hidden;' +\n", + " 'position: relative;' +\n", + " 'resize: both;' +\n", + " 'z-index: 2;'\n", + " );\n", + "\n", + " function on_keyboard_event_closure(name) {\n", + " return function (event) {\n", + " return fig.key_event(event, name);\n", + " };\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'keydown',\n", + " on_keyboard_event_closure('key_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'keyup',\n", + " on_keyboard_event_closure('key_release')\n", + " );\n", + "\n", + " this._canvas_extra_style(canvas_div);\n", + " this.root.appendChild(canvas_div);\n", + "\n", + " var canvas = (this.canvas = document.createElement('canvas'));\n", + " canvas.classList.add('mpl-canvas');\n", + " canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'pointer-events: none;' +\n", + " 'position: relative;' +\n", + " 'z-index: 0;'\n", + " );\n", + "\n", + " this.context = canvas.getContext('2d');\n", + "\n", + " var backingStore =\n", + " this.context.backingStorePixelRatio ||\n", + " this.context.webkitBackingStorePixelRatio ||\n", + " this.context.mozBackingStorePixelRatio ||\n", + " this.context.msBackingStorePixelRatio ||\n", + " this.context.oBackingStorePixelRatio ||\n", + " this.context.backingStorePixelRatio ||\n", + " 1;\n", + "\n", + " this.ratio = (window.devicePixelRatio || 1) / backingStore;\n", + "\n", + " var rubberband_canvas = (this.rubberband_canvas = document.createElement(\n", + " 'canvas'\n", + " ));\n", + " rubberband_canvas.setAttribute(\n", + " 'style',\n", + " 'box-sizing: content-box;' +\n", + " 'left: 0;' +\n", + " 'pointer-events: none;' +\n", + " 'position: absolute;' +\n", + " 'top: 0;' +\n", + " 'z-index: 1;'\n", + " );\n", + "\n", + " // Apply a ponyfill if ResizeObserver is not implemented by browser.\n", + " if (this.ResizeObserver === undefined) {\n", + " if (window.ResizeObserver !== undefined) {\n", + " this.ResizeObserver = window.ResizeObserver;\n", + " } else {\n", + " var obs = _JSXTOOLS_RESIZE_OBSERVER({});\n", + " this.ResizeObserver = obs.ResizeObserver;\n", + " }\n", + " }\n", + "\n", + " this.resizeObserverInstance = new this.ResizeObserver(function (entries) {\n", + " // There's no need to resize if the WebSocket is not connected:\n", + " // - If it is still connecting, then we will get an initial resize from\n", + " // Python once it connects.\n", + " // - If it has disconnected, then resizing will clear the canvas and\n", + " // never get anything back to refill it, so better to not resize and\n", + " // keep something visible.\n", + " if (fig.ws.readyState != 1) {\n", + " return;\n", + " }\n", + " var nentries = entries.length;\n", + " for (var i = 0; i < nentries; i++) {\n", + " var entry = entries[i];\n", + " var width, height;\n", + " if (entry.contentBoxSize) {\n", + " if (entry.contentBoxSize instanceof Array) {\n", + " // Chrome 84 implements new version of spec.\n", + " width = entry.contentBoxSize[0].inlineSize;\n", + " height = entry.contentBoxSize[0].blockSize;\n", + " } else {\n", + " // Firefox implements old version of spec.\n", + " width = entry.contentBoxSize.inlineSize;\n", + " height = entry.contentBoxSize.blockSize;\n", + " }\n", + " } else {\n", + " // Chrome <84 implements even older version of spec.\n", + " width = entry.contentRect.width;\n", + " height = entry.contentRect.height;\n", + " }\n", + "\n", + " // Keep the size of the canvas and rubber band canvas in sync with\n", + " // the canvas container.\n", + " if (entry.devicePixelContentBoxSize) {\n", + " // Chrome 84 implements new version of spec.\n", + " canvas.setAttribute(\n", + " 'width',\n", + " entry.devicePixelContentBoxSize[0].inlineSize\n", + " );\n", + " canvas.setAttribute(\n", + " 'height',\n", + " entry.devicePixelContentBoxSize[0].blockSize\n", + " );\n", + " } else {\n", + " canvas.setAttribute('width', width * fig.ratio);\n", + " canvas.setAttribute('height', height * fig.ratio);\n", + " }\n", + " /* This rescales the canvas back to display pixels, so that it\n", + " * appears correct on HiDPI screens. */\n", + " canvas.style.width = width + 'px';\n", + " canvas.style.height = height + 'px';\n", + "\n", + " rubberband_canvas.setAttribute('width', width);\n", + " rubberband_canvas.setAttribute('height', height);\n", + "\n", + " // And update the size in Python. We ignore the initial 0/0 size\n", + " // that occurs as the element is placed into the DOM, which should\n", + " // otherwise not happen due to the minimum size styling.\n", + " if (width != 0 && height != 0) {\n", + " fig.request_resize(width, height);\n", + " }\n", + " }\n", + " });\n", + " this.resizeObserverInstance.observe(canvas_div);\n", + "\n", + " function on_mouse_event_closure(name) {\n", + " /* User Agent sniffing is bad, but WebKit is busted:\n", + " * https://bugs.webkit.org/show_bug.cgi?id=144526\n", + " * https://bugs.webkit.org/show_bug.cgi?id=181818\n", + " * The worst that happens here is that they get an extra browser\n", + " * selection when dragging, if this check fails to catch them.\n", + " */\n", + " var UA = navigator.userAgent;\n", + " var isWebKit = /AppleWebKit/.test(UA) && !/Chrome/.test(UA);\n", + " if(isWebKit) {\n", + " return function (event) {\n", + " /* This prevents the web browser from automatically changing to\n", + " * the text insertion cursor when the button is pressed. We\n", + " * want to control all of the cursor setting manually through\n", + " * the 'cursor' event from matplotlib */\n", + " event.preventDefault()\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " } else {\n", + " return function (event) {\n", + " return fig.mouse_event(event, name);\n", + " };\n", + " }\n", + " }\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mousedown',\n", + " on_mouse_event_closure('button_press')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseup',\n", + " on_mouse_event_closure('button_release')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'dblclick',\n", + " on_mouse_event_closure('dblclick')\n", + " );\n", + " // Throttle sequential mouse events to 1 every 20ms.\n", + " canvas_div.addEventListener(\n", + " 'mousemove',\n", + " on_mouse_event_closure('motion_notify')\n", + " );\n", + "\n", + " canvas_div.addEventListener(\n", + " 'mouseenter',\n", + " on_mouse_event_closure('figure_enter')\n", + " );\n", + " canvas_div.addEventListener(\n", + " 'mouseleave',\n", + " on_mouse_event_closure('figure_leave')\n", + " );\n", + "\n", + " canvas_div.addEventListener('wheel', function (event) {\n", + " if (event.deltaY < 0) {\n", + " event.step = 1;\n", + " } else {\n", + " event.step = -1;\n", + " }\n", + " on_mouse_event_closure('scroll')(event);\n", + " });\n", + "\n", + " canvas_div.appendChild(canvas);\n", + " canvas_div.appendChild(rubberband_canvas);\n", + "\n", + " this.rubberband_context = rubberband_canvas.getContext('2d');\n", + " this.rubberband_context.strokeStyle = '#000000';\n", + "\n", + " this._resize_canvas = function (width, height, forward) {\n", + " if (forward) {\n", + " canvas_div.style.width = width + 'px';\n", + " canvas_div.style.height = height + 'px';\n", + " }\n", + " };\n", + "\n", + " // Disable right mouse context menu.\n", + " canvas_div.addEventListener('contextmenu', function (_e) {\n", + " event.preventDefault();\n", + " return false;\n", + " });\n", + "\n", + " function set_focus() {\n", + " canvas.focus();\n", + " canvas_div.focus();\n", + " }\n", + "\n", + " window.setTimeout(set_focus, 100);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'mpl-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'mpl-button-group';\n", + " continue;\n", + " }\n", + "\n", + " var button = (fig.buttons[name] = document.createElement('button'));\n", + " button.classList = 'mpl-widget';\n", + " button.setAttribute('role', 'button');\n", + " button.setAttribute('aria-disabled', 'false');\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + "\n", + " var icon_img = document.createElement('img');\n", + " icon_img.src = '_images/' + image + '.png';\n", + " icon_img.srcset = '_images/' + image + '_large.png 2x';\n", + " icon_img.alt = tooltip;\n", + " button.appendChild(icon_img);\n", + "\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " var fmt_picker = document.createElement('select');\n", + " fmt_picker.classList = 'mpl-widget';\n", + " toolbar.appendChild(fmt_picker);\n", + " this.format_dropdown = fmt_picker;\n", + "\n", + " for (var ind in mpl.extensions) {\n", + " var fmt = mpl.extensions[ind];\n", + " var option = document.createElement('option');\n", + " option.selected = fmt === mpl.default_extension;\n", + " option.innerHTML = fmt;\n", + " fmt_picker.appendChild(option);\n", + " }\n", + "\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "};\n", + "\n", + "mpl.figure.prototype.request_resize = function (x_pixels, y_pixels) {\n", + " // Request matplotlib to resize the figure. Matplotlib will then trigger a resize in the client,\n", + " // which will in turn request a refresh of the image.\n", + " this.send_message('resize', { width: x_pixels, height: y_pixels });\n", + "};\n", + "\n", + "mpl.figure.prototype.send_message = function (type, properties) {\n", + " properties['type'] = type;\n", + " properties['figure_id'] = this.id;\n", + " this.ws.send(JSON.stringify(properties));\n", + "};\n", + "\n", + "mpl.figure.prototype.send_draw_message = function () {\n", + " if (!this.waiting) {\n", + " this.waiting = true;\n", + " this.ws.send(JSON.stringify({ type: 'draw', figure_id: this.id }));\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " var format_dropdown = fig.format_dropdown;\n", + " var format = format_dropdown.options[format_dropdown.selectedIndex].value;\n", + " fig.ondownload(fig, format);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_resize = function (fig, msg) {\n", + " var size = msg['size'];\n", + " if (size[0] !== fig.canvas.width || size[1] !== fig.canvas.height) {\n", + " fig._resize_canvas(size[0], size[1], msg['forward']);\n", + " fig.send_message('refresh', {});\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_rubberband = function (fig, msg) {\n", + " var x0 = msg['x0'] / fig.ratio;\n", + " var y0 = (fig.canvas.height - msg['y0']) / fig.ratio;\n", + " var x1 = msg['x1'] / fig.ratio;\n", + " var y1 = (fig.canvas.height - msg['y1']) / fig.ratio;\n", + " x0 = Math.floor(x0) + 0.5;\n", + " y0 = Math.floor(y0) + 0.5;\n", + " x1 = Math.floor(x1) + 0.5;\n", + " y1 = Math.floor(y1) + 0.5;\n", + " var min_x = Math.min(x0, x1);\n", + " var min_y = Math.min(y0, y1);\n", + " var width = Math.abs(x1 - x0);\n", + " var height = Math.abs(y1 - y0);\n", + "\n", + " fig.rubberband_context.clearRect(\n", + " 0,\n", + " 0,\n", + " fig.canvas.width / fig.ratio,\n", + " fig.canvas.height / fig.ratio\n", + " );\n", + "\n", + " fig.rubberband_context.strokeRect(min_x, min_y, width, height);\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_figure_label = function (fig, msg) {\n", + " // Updates the figure title.\n", + " fig.header.textContent = msg['label'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_cursor = function (fig, msg) {\n", + " fig.canvas_div.style.cursor = msg['cursor'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_message = function (fig, msg) {\n", + " fig.message.textContent = msg['message'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_draw = function (fig, _msg) {\n", + " // Request the server to send over a new figure.\n", + " fig.send_draw_message();\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_image_mode = function (fig, msg) {\n", + " fig.image_mode = msg['mode'];\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_history_buttons = function (fig, msg) {\n", + " for (var key in msg) {\n", + " if (!(key in fig.buttons)) {\n", + " continue;\n", + " }\n", + " fig.buttons[key].disabled = !msg[key];\n", + " fig.buttons[key].setAttribute('aria-disabled', !msg[key]);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_navigate_mode = function (fig, msg) {\n", + " if (msg['mode'] === 'PAN') {\n", + " fig.buttons['Pan'].classList.add('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " } else if (msg['mode'] === 'ZOOM') {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.add('active');\n", + " } else {\n", + " fig.buttons['Pan'].classList.remove('active');\n", + " fig.buttons['Zoom'].classList.remove('active');\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Called whenever the canvas gets updated.\n", + " this.send_message('ack', {});\n", + "};\n", + "\n", + "// A function to construct a web socket function for onmessage handling.\n", + "// Called in the figure constructor.\n", + "mpl.figure.prototype._make_on_message_function = function (fig) {\n", + " return function socket_on_message(evt) {\n", + " if (evt.data instanceof Blob) {\n", + " var img = evt.data;\n", + " if (img.type !== 'image/png') {\n", + " /* FIXME: We get \"Resource interpreted as Image but\n", + " * transferred with MIME type text/plain:\" errors on\n", + " * Chrome. But how to set the MIME type? It doesn't seem\n", + " * to be part of the websocket stream */\n", + " img.type = 'image/png';\n", + " }\n", + "\n", + " /* Free the memory for the previous frames */\n", + " if (fig.imageObj.src) {\n", + " (window.URL || window.webkitURL).revokeObjectURL(\n", + " fig.imageObj.src\n", + " );\n", + " }\n", + "\n", + " fig.imageObj.src = (window.URL || window.webkitURL).createObjectURL(\n", + " img\n", + " );\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " } else if (\n", + " typeof evt.data === 'string' &&\n", + " evt.data.slice(0, 21) === 'data:image/png;base64'\n", + " ) {\n", + " fig.imageObj.src = evt.data;\n", + " fig.updated_canvas_event();\n", + " fig.waiting = false;\n", + " return;\n", + " }\n", + "\n", + " var msg = JSON.parse(evt.data);\n", + " var msg_type = msg['type'];\n", + "\n", + " // Call the \"handle_{type}\" callback, which takes\n", + " // the figure and JSON message as its only arguments.\n", + " try {\n", + " var callback = fig['handle_' + msg_type];\n", + " } catch (e) {\n", + " console.log(\n", + " \"No handler for the '\" + msg_type + \"' message type: \",\n", + " msg\n", + " );\n", + " return;\n", + " }\n", + "\n", + " if (callback) {\n", + " try {\n", + " // console.log(\"Handling '\" + msg_type + \"' message: \", msg);\n", + " callback(fig, msg);\n", + " } catch (e) {\n", + " console.log(\n", + " \"Exception inside the 'handler_\" + msg_type + \"' callback:\",\n", + " e,\n", + " e.stack,\n", + " msg\n", + " );\n", + " }\n", + " }\n", + " };\n", + "};\n", + "\n", + "function getModifiers(event) {\n", + " var mods = [];\n", + " if (event.ctrlKey) {\n", + " mods.push('ctrl');\n", + " }\n", + " if (event.altKey) {\n", + " mods.push('alt');\n", + " }\n", + " if (event.shiftKey) {\n", + " mods.push('shift');\n", + " }\n", + " if (event.metaKey) {\n", + " mods.push('meta');\n", + " }\n", + " return mods;\n", + "}\n", + "\n", + "/*\n", + " * return a copy of an object with only non-object keys\n", + " * we need this to avoid circular references\n", + " * https://stackoverflow.com/a/24161582/3208463\n", + " */\n", + "function simpleKeys(original) {\n", + " return Object.keys(original).reduce(function (obj, key) {\n", + " if (typeof original[key] !== 'object') {\n", + " obj[key] = original[key];\n", + " }\n", + " return obj;\n", + " }, {});\n", + "}\n", + "\n", + "mpl.figure.prototype.mouse_event = function (event, name) {\n", + " if (name === 'button_press') {\n", + " this.canvas.focus();\n", + " this.canvas_div.focus();\n", + " }\n", + "\n", + " // from https://stackoverflow.com/q/1114465\n", + " var boundingRect = this.canvas.getBoundingClientRect();\n", + " var x = (event.clientX - boundingRect.left) * this.ratio;\n", + " var y = (event.clientY - boundingRect.top) * this.ratio;\n", + "\n", + " this.send_message(name, {\n", + " x: x,\n", + " y: y,\n", + " button: event.button,\n", + " step: event.step,\n", + " buttons: event.buttons,\n", + " modifiers: getModifiers(event),\n", + " guiEvent: simpleKeys(event),\n", + " });\n", + "\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (_event, _name) {\n", + " // Handle any extra behaviour associated with a key event\n", + "};\n", + "\n", + "mpl.figure.prototype.key_event = function (event, name) {\n", + " // Prevent repeat events\n", + " if (name === 'key_press') {\n", + " if (event.key === this._key) {\n", + " return;\n", + " } else {\n", + " this._key = event.key;\n", + " }\n", + " }\n", + " if (name === 'key_release') {\n", + " this._key = null;\n", + " }\n", + "\n", + " var value = '';\n", + " if (event.ctrlKey && event.key !== 'Control') {\n", + " value += 'ctrl+';\n", + " }\n", + " else if (event.altKey && event.key !== 'Alt') {\n", + " value += 'alt+';\n", + " }\n", + " else if (event.shiftKey && event.key !== 'Shift') {\n", + " value += 'shift+';\n", + " }\n", + "\n", + " value += 'k' + event.key;\n", + "\n", + " this._key_event_extra(event, name);\n", + "\n", + " this.send_message(name, { key: value, guiEvent: simpleKeys(event) });\n", + " return false;\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onclick = function (name) {\n", + " if (name === 'download') {\n", + " this.handle_save(this, null);\n", + " } else {\n", + " this.send_message('toolbar_button', { name: name });\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.toolbar_button_onmouseover = function (tooltip) {\n", + " this.message.textContent = tooltip;\n", + "};\n", + "\n", + "///////////////// REMAINING CONTENT GENERATED BY embed_js.py /////////////////\n", + "// prettier-ignore\n", + "var _JSXTOOLS_RESIZE_OBSERVER=function(A){var t,i=new WeakMap,n=new WeakMap,a=new WeakMap,r=new WeakMap,o=new Set;function s(e){if(!(this instanceof s))throw new TypeError(\"Constructor requires 'new' operator\");i.set(this,e)}function h(){throw new TypeError(\"Function is not a constructor\")}function c(e,t,i,n){e=0 in arguments?Number(arguments[0]):0,t=1 in arguments?Number(arguments[1]):0,i=2 in arguments?Number(arguments[2]):0,n=3 in arguments?Number(arguments[3]):0,this.right=(this.x=this.left=e)+(this.width=i),this.bottom=(this.y=this.top=t)+(this.height=n),Object.freeze(this)}function d(){t=requestAnimationFrame(d);var s=new WeakMap,p=new Set;o.forEach((function(t){r.get(t).forEach((function(i){var r=t instanceof window.SVGElement,o=a.get(t),d=r?0:parseFloat(o.paddingTop),f=r?0:parseFloat(o.paddingRight),l=r?0:parseFloat(o.paddingBottom),u=r?0:parseFloat(o.paddingLeft),g=r?0:parseFloat(o.borderTopWidth),m=r?0:parseFloat(o.borderRightWidth),w=r?0:parseFloat(o.borderBottomWidth),b=u+f,F=d+l,v=(r?0:parseFloat(o.borderLeftWidth))+m,W=g+w,y=r?0:t.offsetHeight-W-t.clientHeight,E=r?0:t.offsetWidth-v-t.clientWidth,R=b+v,z=F+W,M=r?t.width:parseFloat(o.width)-R-E,O=r?t.height:parseFloat(o.height)-z-y;if(n.has(t)){var k=n.get(t);if(k[0]===M&&k[1]===O)return}n.set(t,[M,O]);var S=Object.create(h.prototype);S.target=t,S.contentRect=new c(u,d,M,O),s.has(i)||(s.set(i,[]),p.add(i)),s.get(i).push(S)}))})),p.forEach((function(e){i.get(e).call(e,s.get(e),e)}))}return s.prototype.observe=function(i){if(i instanceof window.Element){r.has(i)||(r.set(i,new Set),o.add(i),a.set(i,window.getComputedStyle(i)));var n=r.get(i);n.has(this)||n.add(this),cancelAnimationFrame(t),t=requestAnimationFrame(d)}},s.prototype.unobserve=function(i){if(i instanceof window.Element&&r.has(i)){var n=r.get(i);n.has(this)&&(n.delete(this),n.size||(r.delete(i),o.delete(i))),n.size||r.delete(i),o.size||cancelAnimationFrame(t)}},A.DOMRectReadOnly=c,A.ResizeObserver=s,A.ResizeObserverEntry=h,A}; // eslint-disable-line\n", + "mpl.toolbar_items = [[\"Home\", \"Reset original view\", \"fa fa-home\", \"home\"], [\"Back\", \"Back to previous view\", \"fa fa-arrow-left\", \"back\"], [\"Forward\", \"Forward to next view\", \"fa fa-arrow-right\", \"forward\"], [\"\", \"\", \"\", \"\"], [\"Pan\", \"Left button pans, Right button zooms\\nx/y fixes axis, CTRL fixes aspect\", \"fa fa-arrows\", \"pan\"], [\"Zoom\", \"Zoom to rectangle\\nx/y fixes axis\", \"fa fa-square-o\", \"zoom\"], [\"\", \"\", \"\", \"\"], [\"Download\", \"Download plot\", \"fa fa-floppy-o\", \"download\"]];\n", + "\n", + "mpl.extensions = [\"eps\", \"jpeg\", \"pgf\", \"pdf\", \"png\", \"ps\", \"raw\", \"svg\", \"tif\", \"webp\"];\n", + "\n", + "mpl.default_extension = \"png\";/* global mpl */\n", + "\n", + "var comm_websocket_adapter = function (comm) {\n", + " // Create a \"websocket\"-like object which calls the given IPython comm\n", + " // object with the appropriate methods. Currently this is a non binary\n", + " // socket, so there is still some room for performance tuning.\n", + " var ws = {};\n", + "\n", + " ws.binaryType = comm.kernel.ws.binaryType;\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " function updateReadyState(_event) {\n", + " if (comm.kernel.ws) {\n", + " ws.readyState = comm.kernel.ws.readyState;\n", + " } else {\n", + " ws.readyState = 3; // Closed state.\n", + " }\n", + " }\n", + " comm.kernel.ws.addEventListener('open', updateReadyState);\n", + " comm.kernel.ws.addEventListener('close', updateReadyState);\n", + " comm.kernel.ws.addEventListener('error', updateReadyState);\n", + "\n", + " ws.close = function () {\n", + " comm.close();\n", + " };\n", + " ws.send = function (m) {\n", + " //console.log('sending', m);\n", + " comm.send(m);\n", + " };\n", + " // Register the callback with on_msg.\n", + " comm.on_msg(function (msg) {\n", + " //console.log('receiving', msg['content']['data'], msg);\n", + " var data = msg['content']['data'];\n", + " if (data['blob'] !== undefined) {\n", + " data = {\n", + " data: new Blob(msg['buffers'], { type: data['blob'] }),\n", + " };\n", + " }\n", + " // Pass the mpl event to the overridden (by mpl) onmessage function.\n", + " ws.onmessage(data);\n", + " });\n", + " return ws;\n", + "};\n", + "\n", + "mpl.mpl_figure_comm = function (comm, msg) {\n", + " // This is the function which gets called when the mpl process\n", + " // starts-up an IPython Comm through the \"matplotlib\" channel.\n", + "\n", + " var id = msg.content.data.id;\n", + " // Get hold of the div created by the display call when the Comm\n", + " // socket was opened in Python.\n", + " var element = document.getElementById(id);\n", + " var ws_proxy = comm_websocket_adapter(comm);\n", + "\n", + " function ondownload(figure, _format) {\n", + " window.open(figure.canvas.toDataURL());\n", + " }\n", + "\n", + " var fig = new mpl.figure(id, ws_proxy, ondownload, element);\n", + "\n", + " // Call onopen now - mpl needs it, as it is assuming we've passed it a real\n", + " // web socket which is closed, not our websocket->open comm proxy.\n", + " ws_proxy.onopen();\n", + "\n", + " fig.parent_element = element;\n", + " fig.cell_info = mpl.find_output_cell(\"
\");\n", + " if (!fig.cell_info) {\n", + " console.error('Failed to find cell for figure', id, fig);\n", + " return;\n", + " }\n", + " fig.cell_info[0].output_area.element.on(\n", + " 'cleared',\n", + " { fig: fig },\n", + " fig._remove_fig_handler\n", + " );\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_close = function (fig, msg) {\n", + " var width = fig.canvas.width / fig.ratio;\n", + " fig.cell_info[0].output_area.element.off(\n", + " 'cleared',\n", + " fig._remove_fig_handler\n", + " );\n", + " fig.resizeObserverInstance.unobserve(fig.canvas_div);\n", + "\n", + " // Update the output cell to use the data from the current canvas.\n", + " fig.push_to_output();\n", + " var dataURL = fig.canvas.toDataURL();\n", + " // Re-enable the keyboard manager in IPython - without this line, in FF,\n", + " // the notebook keyboard shortcuts fail.\n", + " IPython.keyboard_manager.enable();\n", + " fig.parent_element.innerHTML =\n", + " '';\n", + " fig.close_ws(fig, msg);\n", + "};\n", + "\n", + "mpl.figure.prototype.close_ws = function (fig, msg) {\n", + " fig.send_message('closing', msg);\n", + " // fig.ws.close()\n", + "};\n", + "\n", + "mpl.figure.prototype.push_to_output = function (_remove_interactive) {\n", + " // Turn the data on the canvas into data in the output cell.\n", + " var width = this.canvas.width / this.ratio;\n", + " var dataURL = this.canvas.toDataURL();\n", + " this.cell_info[1]['text/html'] =\n", + " '';\n", + "};\n", + "\n", + "mpl.figure.prototype.updated_canvas_event = function () {\n", + " // Tell IPython that the notebook contents must change.\n", + " IPython.notebook.set_dirty(true);\n", + " this.send_message('ack', {});\n", + " var fig = this;\n", + " // Wait a second, then push the new image to the DOM so\n", + " // that it is saved nicely (might be nice to debounce this).\n", + " setTimeout(function () {\n", + " fig.push_to_output();\n", + " }, 1000);\n", + "};\n", + "\n", + "mpl.figure.prototype._init_toolbar = function () {\n", + " var fig = this;\n", + "\n", + " var toolbar = document.createElement('div');\n", + " toolbar.classList = 'btn-toolbar';\n", + " this.root.appendChild(toolbar);\n", + "\n", + " function on_click_closure(name) {\n", + " return function (_event) {\n", + " return fig.toolbar_button_onclick(name);\n", + " };\n", + " }\n", + "\n", + " function on_mouseover_closure(tooltip) {\n", + " return function (event) {\n", + " if (!event.currentTarget.disabled) {\n", + " return fig.toolbar_button_onmouseover(tooltip);\n", + " }\n", + " };\n", + " }\n", + "\n", + " fig.buttons = {};\n", + " var buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " var button;\n", + " for (var toolbar_ind in mpl.toolbar_items) {\n", + " var name = mpl.toolbar_items[toolbar_ind][0];\n", + " var tooltip = mpl.toolbar_items[toolbar_ind][1];\n", + " var image = mpl.toolbar_items[toolbar_ind][2];\n", + " var method_name = mpl.toolbar_items[toolbar_ind][3];\n", + "\n", + " if (!name) {\n", + " /* Instead of a spacer, we start a new button group. */\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + " buttonGroup = document.createElement('div');\n", + " buttonGroup.classList = 'btn-group';\n", + " continue;\n", + " }\n", + "\n", + " button = fig.buttons[name] = document.createElement('button');\n", + " button.classList = 'btn btn-default';\n", + " button.href = '#';\n", + " button.title = name;\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', on_click_closure(method_name));\n", + " button.addEventListener('mouseover', on_mouseover_closure(tooltip));\n", + " buttonGroup.appendChild(button);\n", + " }\n", + "\n", + " if (buttonGroup.hasChildNodes()) {\n", + " toolbar.appendChild(buttonGroup);\n", + " }\n", + "\n", + " // Add the status bar.\n", + " var status_bar = document.createElement('span');\n", + " status_bar.classList = 'mpl-message pull-right';\n", + " toolbar.appendChild(status_bar);\n", + " this.message = status_bar;\n", + "\n", + " // Add the close button to the window.\n", + " var buttongrp = document.createElement('div');\n", + " buttongrp.classList = 'btn-group inline pull-right';\n", + " button = document.createElement('button');\n", + " button.classList = 'btn btn-mini btn-primary';\n", + " button.href = '#';\n", + " button.title = 'Stop Interaction';\n", + " button.innerHTML = '';\n", + " button.addEventListener('click', function (_evt) {\n", + " fig.handle_close(fig, {});\n", + " });\n", + " button.addEventListener(\n", + " 'mouseover',\n", + " on_mouseover_closure('Stop Interaction')\n", + " );\n", + " buttongrp.appendChild(button);\n", + " var titlebar = this.root.querySelector('.ui-dialog-titlebar');\n", + " titlebar.insertBefore(buttongrp, titlebar.firstChild);\n", + "};\n", + "\n", + "mpl.figure.prototype._remove_fig_handler = function (event) {\n", + " var fig = event.data.fig;\n", + " if (event.target !== this) {\n", + " // Ignore bubbled events from children.\n", + " return;\n", + " }\n", + " fig.close_ws(fig, {});\n", + "};\n", + "\n", + "mpl.figure.prototype._root_extra_style = function (el) {\n", + " el.style.boxSizing = 'content-box'; // override notebook setting of border-box.\n", + "};\n", + "\n", + "mpl.figure.prototype._canvas_extra_style = function (el) {\n", + " // this is important to make the div 'focusable\n", + " el.setAttribute('tabindex', 0);\n", + " // reach out to IPython and tell the keyboard manager to turn it's self\n", + " // off when our div gets focus\n", + "\n", + " // location in version 3\n", + " if (IPython.notebook.keyboard_manager) {\n", + " IPython.notebook.keyboard_manager.register_events(el);\n", + " } else {\n", + " // location in version 2\n", + " IPython.keyboard_manager.register_events(el);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype._key_event_extra = function (event, _name) {\n", + " // Check for shift+enter\n", + " if (event.shiftKey && event.which === 13) {\n", + " this.canvas_div.blur();\n", + " // select the cell after this one\n", + " var index = IPython.notebook.find_cell_index(this.cell_info[0]);\n", + " IPython.notebook.select(index + 1);\n", + " }\n", + "};\n", + "\n", + "mpl.figure.prototype.handle_save = function (fig, _msg) {\n", + " fig.ondownload(fig, null);\n", + "};\n", + "\n", + "mpl.find_output_cell = function (html_output) {\n", + " // Return the cell and output element which can be found *uniquely* in the notebook.\n", + " // Note - this is a bit hacky, but it is done because the \"notebook_saving.Notebook\"\n", + " // IPython event is triggered only after the cells have been serialised, which for\n", + " // our purposes (turning an active figure into a static one), is too late.\n", + " var cells = IPython.notebook.get_cells();\n", + " var ncells = cells.length;\n", + " for (var i = 0; i < ncells; i++) {\n", + " var cell = cells[i];\n", + " if (cell.cell_type === 'code') {\n", + " for (var j = 0; j < cell.output_area.outputs.length; j++) {\n", + " var data = cell.output_area.outputs[j];\n", + " if (data.data) {\n", + " // IPython >= 3 moved mimebundle to data attribute of output\n", + " data = data.data;\n", + " }\n", + " if (data['text/html'] === html_output) {\n", + " return [cell, data, j];\n", + " }\n", + " }\n", + " }\n", + " }\n", + "};\n", + "\n", + "// Register the function which deals with the matplotlib target/channel.\n", + "// The kernel may be null if the page has been refreshed.\n", + "if (IPython.notebook.kernel !== null) {\n", + " IPython.notebook.kernel.comm_manager.register_target(\n", + " 'matplotlib',\n", + " mpl.mpl_figure_comm\n", + " );\n", + "}\n" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ], + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "mainseq = bb.Sequence()\n", + "mainseq.setSR(SR)\n", + "\n", + "mainseq.addSubSequence(1, seq)\n", + "mainseq.addSubSequence(2, seq2)\n", + "mainseq.addSubSequence(3, seq3)\n", + "\n", + "mainseq.setSequencingNumberOfRepetitions(1, 25)\n", + "mainseq.setSequencingNumberOfRepetitions(2, 25)\n", + "mainseq.setSequencingNumberOfRepetitions(3, 25)\n", + "\n", + "plotter(mainseq)\n", + "\n", + "# The plotting does not show the details of the subsequence,\n", + "# but it DOES show the min and max voltages of a subsequence\n", + "# as grey lines" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "execution": { + "iopub.execute_input": "2025-01-21T06:30:26.862594Z", + "iopub.status.busy": "2025-01-21T06:30:26.862249Z", + "iopub.status.idle": "2025-01-21T06:30:26.866919Z", + "shell.execute_reply": "2025-01-21T06:30:26.866367Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "13560" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# The number of points is still low\n", + "mainseq.points" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.11" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/examples/index.html b/examples/index.html new file mode 100644 index 000000000..e8d11fb45 --- /dev/null +++ b/examples/index.html @@ -0,0 +1,341 @@ + + + + + + + + + Broadbean Examples - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+ +
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/genindex.html b/genindex.html new file mode 100644 index 000000000..296cac7ea --- /dev/null +++ b/genindex.html @@ -0,0 +1,743 @@ + + + + + + + Index - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+ +
+

Index

+
A | B | C | D | E | F | G | I | L | M | N | O | P | R | S | V | W
+
+
+

A

+ + + +
+
+ +
+

B

+ + + +
    +
  • + broadbean.plotting + +
  • +
  • + broadbean.ripasso + +
  • +
  • + broadbean.sequence + +
  • +
  • + broadbean.tools + +
  • +
+
+ +
+

C

+ + + +
+
+ +
+

D

+ + + +
+
+ +
+

E

+ + + +
+
+ +
+

F

+ + +
+
+ +
+

G

+ + + +
+
+ +
+

I

+ + + +
+
+ +
+

L

+ + + +
+
+ +
+

M

+ + +
+
+ +
+

N

+ + +
+
+ +
+

O

+ + + +
+
+ +
+

P

+ + + +
+
+ +
+

R

+ + + +
+
+ +
+

S

+ + + +
+
+ +
+

V

+ + +
+
+ +
+

W

+ + +
+
+ + +
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 000000000..42cebfe6e --- /dev/null +++ b/index.html @@ -0,0 +1,395 @@ + + + + + + + + + broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+

Welcome to broadbean’s documentation!

+

The broadbean package is a tool for creating and manipulating pulse +sequences.The broadbean module lets the user compose and manipulate pulse sequences. +The aim of the module is to reduce pulse building to the logical minimum of +specifications so that building and manipulation become as easy as saying “Gimme +a square wave, then a ramp, then a sine, and then wait for 10 ms” and, in particular, +“Do the same thing again, but now with the sine having twice the frequency it had before”.

+

The little extra module called ripasso performs frequency filtering and frequency filter +compensation. It could be useful in a general setting and is therefore factored out +to its own module.

+

The name: The broad bean is one of my favourite pulses.

+
+

Documentation

+ +
+

Indices and tables

+ +
+
+ +
+
+ +
+ +
+
+ + + + + + + + \ No newline at end of file diff --git a/objects.inv b/objects.inv new file mode 100644 index 000000000..5a2c1d025 Binary files /dev/null and b/objects.inv differ diff --git a/py-modindex.html b/py-modindex.html new file mode 100644 index 000000000..1251de470 --- /dev/null +++ b/py-modindex.html @@ -0,0 +1,357 @@ + + + + + + + Python Module Index - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+ +
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + \ No newline at end of file diff --git a/search.html b/search.html new file mode 100644 index 000000000..39ba2b4ac --- /dev/null +++ b/search.html @@ -0,0 +1,311 @@ + + + + + + + + + +Search - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+
+ +
+ +
+
+ + + +
+ +
+
+
+ + +
+
+ + Made with Sphinx and @pradyunsg's + + Furo + +
+
+ +
+
+ +
+
+ +
+
+ + + + + + + + + \ No newline at end of file diff --git a/searchindex.js b/searchindex.js new file mode 100644 index 000000000..42d2c2481 --- /dev/null +++ b/searchindex.js @@ -0,0 +1 @@ +Search.setIndex({"alltitles": {"Basic blueprinting": [[7, "Basic-blueprinting"]], "Behind The Scenes:": [[2, "behind-the-scenes"]], "Blueprints": [[7, "Blueprints"]], "Breaking Changes:": [[1, "breaking-changes"], [2, "breaking-changes"]], "Broadbean Examples": [[9, null]], "Building a BluePrint": [[4, "Building-a-BluePrint"]], "Changelog for broadbean 0.10.0": [[1, null]], "Changelog for broadbean 0.11.0": [[2, null]], "Changelogs": [[3, null]], "Compressed": [[8, "Compressed"]], "Create a broadbean environment": [[11, "create-a-broadbean-environment"]], "Delays and filter compensation": [[7, "Delays-and-filter-compensation"]], "Documentation": [[10, null]], "Ekstras": [[4, "Ekstras"]], "Elements": [[7, "Elements"]], "Example 1: Compression": [[8, "Example-1:-Compression"]], "Filter compensation with the ripasso module": [[5, null]], "Getting Started": [[11, null]], "Improved:": [[2, "improved"]], "Indices and tables": [[10, "indices-and-tables"]], "Installation": [[11, "installation"]], "Installing the latest broadbean release": [[11, "installing-the-latest-broadbean-release"]], "Intro to Blueprints:": [[7, "Intro-to-Blueprints:"]], "Intro to Elements:": [[7, "Intro-to-Elements:"]], "Intro to Normal segments:": [[7, "Intro-to-Normal-segments:"]], "Intro to Sequences:": [[7, "Intro-to-Sequences:"]], "Intro to Special segments:": [[7, "Intro-to-Special-segments:"]], "Introduction": [[6, null]], "Keeping your environment up to date": [[11, "keeping-your-environment-up-to-date"]], "Lingo": [[7, "Lingo"]], "Markers": [[7, "Markers"]], "Modifying blueprints": [[7, "Modifying-blueprints"]], "Module contents": [[0, "module-broadbean"]], "New:": [[1, "new"], [2, "new"]], "Now using subsequences": [[8, "Now-using-subsequences"]], "Pulsebuilding tutorial": [[7, null]], "RC Filters": [[5, "RC-Filters"]], "Read and Write BluePrint from JSON file Tutorial": [[4, "Read-and-Write-BluePrint-from-JSON-file-Tutorial"]], "Read and Write Element from JSON file Tutorial": [[4, "Read-and-Write-Element-from-JSON-file-Tutorial"]], "Read and Write Sequence from JSON file Tutorial": [[4, "Read-and-Write-Sequence-from-JSON-file-Tutorial"]], "Read and Write from JSON file Tutorial": [[4, null]], "Reading the Sequence back from the file": [[4, "Reading-the-Sequence-back-from-the-file"]], "Requirements": [[11, "requirements"]], "Segments:": [[7, "Segments:"]], "Sequences": [[7, "Sequences"]], "Sequences varying parameters": [[7, "Sequences-varying-parameters"]], "Setting up your development environment": [[11, "setting-up-your-development-environment"]], "Special segments": [[7, "Special-segments"]], "Step 1: Make a sequence": [[6, "Step-1:-Make-a-sequence"]], "Step 2: Initialise the driver": [[6, "Step-2:-Initialise-the-driver"]], "Step 3: Make output for the driver": [[6, "Step-3:-Make-output-for-the-driver"]], "Step 4: Build, send, load, and assign a .seqx file": [[6, "Step-4:-Build,-send,-load,-and-assign-a-.seqx-file"]], "Step 5: Play it": [[6, "Step-5:-Play-it"]], "Submodules": [[0, "submodules"]], "Subsequences": [[8, null]], "Table of Contents": [[7, "Table-of-Contents"]], "Tektronix AWG 5014 output": [[7, "Tektronix-AWG-5014-output"]], "Test if the Sequences are identical": [[4, "Test-if-the-Sequences-are-identical"]], "Uncompressed": [[8, "Uncompressed"]], "Updating Broadbean": [[11, "updating-broadbean"]], "User-supplied transfer function": [[5, "User-supplied-transfer-function"]], "Using broadbean": [[11, "using-broadbean"]], "Writing the Sequence to a file": [[4, "Writing-the-Sequence-to-a-file"]], "broadbean package": [[0, null]], "broadbean.blueprint module": [[0, "module-broadbean.blueprint"]], "broadbean.broadbean module": [[0, "module-broadbean.broadbean"]], "broadbean.element module": [[0, "module-broadbean.element"]], "broadbean.plotting module": [[0, "module-broadbean.plotting"]], "broadbean.ripasso module": [[0, "module-broadbean.ripasso"]], "broadbean.sequence module": [[0, "module-broadbean.sequence"]], "broadbean.tools module": [[0, "module-broadbean.tools"]]}, "docnames": ["api/generated/broadbean", "changes/0.10.0", "changes/0.11.0", "changes/index", "examples/Example_Write_Read_JSON", "examples/Filter_compensation", "examples/Making_output_for_Tektronix_AWG70000A", "examples/Pulse_Building_Tutorial", "examples/Subsequences", "examples/index", "index", "start/index"], "envversion": {"nbsphinx": 4, "sphinx": 64, "sphinx.domains.c": 3, "sphinx.domains.changeset": 1, "sphinx.domains.citation": 1, "sphinx.domains.cpp": 9, "sphinx.domains.index": 1, "sphinx.domains.javascript": 3, "sphinx.domains.math": 2, "sphinx.domains.python": 4, "sphinx.domains.rst": 2, "sphinx.domains.std": 2, "sphinx.ext.intersphinx": 1, "sphinx.ext.todo": 2, "sphinx.ext.viewcode": 1}, "filenames": ["api/generated/broadbean.rst", "changes/0.10.0.rst", "changes/0.11.0.rst", "changes/index.rst", "examples/Example_Write_Read_JSON.ipynb", "examples/Filter_compensation.ipynb", "examples/Making_output_for_Tektronix_AWG70000A.ipynb", "examples/Pulse_Building_Tutorial.ipynb", "examples/Subsequences.ipynb", "examples/index.rst", "index.rst", "start/index.rst"], "indexentries": {"addarray() (broadbean.element.element method)": [[0, "broadbean.element.Element.addArray", false]], "addblueprint() (broadbean.element.element method)": [[0, "broadbean.element.Element.addBluePrint", false]], "addelement() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.addElement", false]], "addflags() (broadbean.element.element method)": [[0, "broadbean.element.Element.addFlags", false]], "addsubsequence() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.addSubSequence", false]], "applycustomtransferfunction() (in module broadbean.ripasso)": [[0, "broadbean.ripasso.applyCustomTransferFunction", false]], "applyinversercfilter() (in module broadbean.ripasso)": [[0, "broadbean.ripasso.applyInverseRCFilter", false]], "applyrcfilter() (in module broadbean.ripasso)": [[0, "broadbean.ripasso.applyRCFilter", false]], "arb_func() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.arb_func", false]], "blueprint (class in broadbean.blueprint)": [[0, "broadbean.blueprint.BluePrint", false]], "blueprint_from_description() (broadbean.blueprint.blueprint class method)": [[0, "broadbean.blueprint.BluePrint.blueprint_from_description", false]], "broadbean": [[0, "module-broadbean", false]], "broadbean.blueprint": [[0, "module-broadbean.blueprint", false]], "broadbean.broadbean": [[0, "module-broadbean.broadbean", false]], "broadbean.element": [[0, "module-broadbean.element", false]], "broadbean.plotting": [[0, "module-broadbean.plotting", false]], "broadbean.ripasso": [[0, "module-broadbean.ripasso", false]], "broadbean.sequence": [[0, "module-broadbean.sequence", false]], "broadbean.tools": [[0, "module-broadbean.tools", false]], "changearg() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.changeArg", false]], "changearg() (broadbean.element.element method)": [[0, "broadbean.element.Element.changeArg", false]], "changeduration() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.changeDuration", false]], "changeduration() (broadbean.element.element method)": [[0, "broadbean.element.Element.changeDuration", false]], "channels (broadbean.element.element property)": [[0, "broadbean.element.Element.channels", false]], "channels (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.channels", false]], "checkconsistency() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.checkConsistency", false]], "copy() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.copy", false]], "copy() (broadbean.element.element method)": [[0, "broadbean.element.Element.copy", false]], "copy() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.copy", false]], "description (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.description", false]], "description (broadbean.element.element property)": [[0, "broadbean.element.Element.description", false]], "description (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.description", false]], "duration (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.duration", false]], "duration (broadbean.element.element property)": [[0, "broadbean.element.Element.duration", false]], "duration (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.duration", false]], "durations (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.durations", false]], "element (class in broadbean.element)": [[0, "broadbean.element.Element", false]], "element() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.element", false]], "element_from_description() (broadbean.element.element class method)": [[0, "broadbean.element.Element.element_from_description", false]], "elementdurationerror": [[0, "broadbean.element.ElementDurationError", false]], "forge() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.forge", false]], "gaussian() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.gaussian", false]], "gaussian_smooth_cutoff() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.gaussian_smooth_cutoff", false]], "getarrays() (broadbean.element.element method)": [[0, "broadbean.element.Element.getArrays", false]], "getsiscalingandprefix() (in module broadbean.plotting)": [[0, "broadbean.plotting.getSIScalingAndPrefix", false]], "init_from_json() (broadbean.blueprint.blueprint class method)": [[0, "broadbean.blueprint.BluePrint.init_from_json", false]], "init_from_json() (broadbean.element.element class method)": [[0, "broadbean.element.Element.init_from_json", false]], "init_from_json() (broadbean.sequence.sequence class method)": [[0, "broadbean.sequence.Sequence.init_from_json", false]], "insertsegment() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.insertSegment", false]], "invalidforgedsequenceerror": [[0, "broadbean.sequence.InvalidForgedSequenceError", false]], "length_segments (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.length_segments", false]], "length_sequenceelements (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.length_sequenceelements", false]], "makelinearlyvaryingsequence() (in module broadbean.tools)": [[0, "broadbean.tools.makeLinearlyVaryingSequence", false]], "makevaryingsequence() (in module broadbean.tools)": [[0, "broadbean.tools.makeVaryingSequence", false]], "marked_for_deletion() (in module broadbean.broadbean)": [[0, "broadbean.broadbean.marked_for_deletion", false]], "missingfrequencieserror": [[0, "broadbean.ripasso.MissingFrequenciesError", false]], "module": [[0, "module-broadbean", false], [0, "module-broadbean.blueprint", false], [0, "module-broadbean.broadbean", false], [0, "module-broadbean.element", false], [0, "module-broadbean.plotting", false], [0, "module-broadbean.ripasso", false], [0, "module-broadbean.sequence", false], [0, "module-broadbean.tools", false]], "name (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.name", false]], "outputforawgfile() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.outputForAWGFile", false]], "outputforseqxfile() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.outputForSEQXFile", false]], "outputforseqxfilewithflags() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.outputForSEQXFileWithFlags", false]], "plotter() (in module broadbean.plotting)": [[0, "broadbean.plotting.plotter", false]], "points (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.points", false]], "points (broadbean.element.element property)": [[0, "broadbean.element.Element.points", false]], "points (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.points", false]], "pulseatoms (class in broadbean.broadbean)": [[0, "broadbean.broadbean.PulseAtoms", false]], "ramp() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.ramp", false]], "removesegment() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.removeSegment", false]], "removesegmentmarker() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.removeSegmentMarker", false]], "repeatandvarysequence() (in module broadbean.tools)": [[0, "broadbean.tools.repeatAndVarySequence", false]], "segmentdurationerror": [[0, "broadbean.blueprint.SegmentDurationError", false]], "sequence (class in broadbean.sequence)": [[0, "broadbean.sequence.Sequence", false]], "sequence_from_description() (broadbean.sequence.sequence class method)": [[0, "broadbean.sequence.Sequence.sequence_from_description", false]], "sequencecompatibilityerror": [[0, "broadbean.sequence.SequenceCompatibilityError", false]], "sequenceconsistencyerror": [[0, "broadbean.sequence.SequenceConsistencyError", false]], "sequencingerror": [[0, "broadbean.sequence.SequencingError", false]], "setchannelamplitude() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setChannelAmplitude", false]], "setchanneldelay() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setChannelDelay", false]], "setchannelfiltercompensation() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setChannelFilterCompensation", false]], "setchanneloffset() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setChannelOffset", false]], "setchannelvoltagerange() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setChannelVoltageRange", false]], "setsegmentmarker() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.setSegmentMarker", false]], "setsequencesettings() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequenceSettings", false]], "setsequencingeventinput() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequencingEventInput", false]], "setsequencingeventjumptarget() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequencingEventJumpTarget", false]], "setsequencinggoto() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequencingGoto", false]], "setsequencingnumberofrepetitions() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequencingNumberOfRepetitions", false]], "setsequencingtriggerwait() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSequencingTriggerWait", false]], "setsr() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.setSR", false]], "setsr() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.setSR", false]], "showprint() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.showPrint", false]], "sine() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.sine", false]], "specificationinconsistencyerror": [[0, "broadbean.sequence.SpecificationInconsistencyError", false]], "sr (broadbean.blueprint.blueprint property)": [[0, "broadbean.blueprint.BluePrint.SR", false]], "sr (broadbean.element.element property)": [[0, "broadbean.element.Element.SR", false]], "sr (broadbean.sequence.sequence property)": [[0, "broadbean.sequence.Sequence.SR", false]], "validatedurations() (broadbean.element.element method)": [[0, "broadbean.element.Element.validateDurations", false]], "waituntil() (broadbean.broadbean.pulseatoms static method)": [[0, "broadbean.broadbean.PulseAtoms.waituntil", false]], "write_to_json() (broadbean.blueprint.blueprint method)": [[0, "broadbean.blueprint.BluePrint.write_to_json", false]], "write_to_json() (broadbean.element.element method)": [[0, "broadbean.element.Element.write_to_json", false]], "write_to_json() (broadbean.sequence.sequence method)": [[0, "broadbean.sequence.Sequence.write_to_json", false]]}, "objects": {"": [[0, 0, 0, "-", "broadbean"]], "broadbean": [[0, 0, 0, "-", "blueprint"], [0, 0, 0, "-", "broadbean"], [0, 0, 0, "-", "element"], [0, 0, 0, "-", "plotting"], [0, 0, 0, "-", "ripasso"], [0, 0, 0, "-", "sequence"], [0, 0, 0, "-", "tools"]], "broadbean.blueprint": [[0, 1, 1, "", "BluePrint"], [0, 4, 1, "", "SegmentDurationError"]], "broadbean.blueprint.BluePrint": [[0, 2, 1, "", "SR"], [0, 3, 1, "", "blueprint_from_description"], [0, 3, 1, "", "changeArg"], [0, 3, 1, "", "changeDuration"], [0, 3, 1, "", "copy"], [0, 2, 1, "", "description"], [0, 2, 1, "", "duration"], [0, 2, 1, "", "durations"], [0, 3, 1, "", "init_from_json"], [0, 3, 1, "", "insertSegment"], [0, 2, 1, "", "length_segments"], [0, 2, 1, "", "points"], [0, 3, 1, "", "removeSegment"], [0, 3, 1, "", "removeSegmentMarker"], [0, 3, 1, "", "setSR"], [0, 3, 1, "", "setSegmentMarker"], [0, 3, 1, "", "showPrint"], [0, 3, 1, "", "write_to_json"]], "broadbean.broadbean": [[0, 1, 1, "", "PulseAtoms"], [0, 5, 1, "", "marked_for_deletion"]], "broadbean.broadbean.PulseAtoms": [[0, 3, 1, "", "arb_func"], [0, 3, 1, "", "gaussian"], [0, 3, 1, "", "gaussian_smooth_cutoff"], [0, 3, 1, "", "ramp"], [0, 3, 1, "", "sine"], [0, 3, 1, "", "waituntil"]], "broadbean.element": [[0, 1, 1, "", "Element"], [0, 4, 1, "", "ElementDurationError"]], "broadbean.element.Element": [[0, 2, 1, "", "SR"], [0, 3, 1, "", "addArray"], [0, 3, 1, "", "addBluePrint"], [0, 3, 1, "", "addFlags"], [0, 3, 1, "", "changeArg"], [0, 3, 1, "", "changeDuration"], [0, 2, 1, "", "channels"], [0, 3, 1, "", "copy"], [0, 2, 1, "", "description"], [0, 2, 1, "", "duration"], [0, 3, 1, "", "element_from_description"], [0, 3, 1, "", "getArrays"], [0, 3, 1, "", "init_from_json"], [0, 2, 1, "", "points"], [0, 3, 1, "", "validateDurations"], [0, 3, 1, "", "write_to_json"]], "broadbean.plotting": [[0, 5, 1, "", "getSIScalingAndPrefix"], [0, 5, 1, "", "plotter"]], "broadbean.ripasso": [[0, 4, 1, "", "MissingFrequenciesError"], [0, 5, 1, "", "applyCustomTransferFunction"], [0, 5, 1, "", "applyInverseRCFilter"], [0, 5, 1, "", "applyRCFilter"]], "broadbean.sequence": [[0, 4, 1, "", "InvalidForgedSequenceError"], [0, 1, 1, "", "Sequence"], [0, 4, 1, "", "SequenceCompatibilityError"], [0, 4, 1, "", "SequenceConsistencyError"], [0, 4, 1, "", "SequencingError"], [0, 4, 1, "", "SpecificationInconsistencyError"]], "broadbean.sequence.Sequence": [[0, 2, 1, "", "SR"], [0, 3, 1, "", "addElement"], [0, 3, 1, "", "addSubSequence"], [0, 2, 1, "", "channels"], [0, 3, 1, "", "checkConsistency"], [0, 3, 1, "", "copy"], [0, 2, 1, "", "description"], [0, 2, 1, "", "duration"], [0, 3, 1, "", "element"], [0, 3, 1, "", "forge"], [0, 3, 1, "", "init_from_json"], [0, 2, 1, "", "length_sequenceelements"], [0, 2, 1, "", "name"], [0, 3, 1, "", "outputForAWGFile"], [0, 3, 1, "", "outputForSEQXFile"], [0, 3, 1, "", "outputForSEQXFileWithFlags"], [0, 2, 1, "", "points"], [0, 3, 1, "", "sequence_from_description"], [0, 3, 1, "", "setChannelAmplitude"], [0, 3, 1, "", "setChannelDelay"], [0, 3, 1, "", "setChannelFilterCompensation"], [0, 3, 1, "", "setChannelOffset"], [0, 3, 1, "", "setChannelVoltageRange"], [0, 3, 1, "", "setSR"], [0, 3, 1, "", "setSequenceSettings"], [0, 3, 1, "", "setSequencingEventInput"], [0, 3, 1, "", "setSequencingEventJumpTarget"], [0, 3, 1, "", "setSequencingGoto"], [0, 3, 1, "", "setSequencingNumberOfRepetitions"], [0, 3, 1, "", "setSequencingTriggerWait"], [0, 3, 1, "", "write_to_json"]], "broadbean.tools": [[0, 5, 1, "", "makeLinearlyVaryingSequence"], [0, 5, 1, "", "makeVaryingSequence"], [0, 5, 1, "", "repeatAndVarySequence"]]}, "objnames": {"0": ["py", "module", "Python module"], "1": ["py", "class", "Python class"], "2": ["py", "property", "Python property"], "3": ["py", "method", "Python method"], "4": ["py", "exception", "Python exception"], "5": ["py", "function", "Python function"]}, "objtypes": {"0": "py:module", "1": "py:class", "2": "py:property", "3": "py:method", "4": "py:exception", "5": "py:function"}, "terms": {"": [0, 2, 5, 6, 7, 8, 10], "0": [0, 3, 4, 5, 6, 7, 8, 10], "001": 7, "01": 5, "02": 5, "05": 6, "06": 7, "07": 7, "0x7f75b0a63f60": 7, "1": [0, 1, 4, 5, 7], "10": [2, 3, 4, 5, 6, 7, 8, 10, 11], "100": [7, 8], "1000": 5, "10000": 7, "1000000000": 7, "100e": [4, 7], "101": 2, "102": 2, "103": 2, "107": 2, "108": 2, "109": 2, "10e": [4, 6, 7], "10e3": 5, "11": [3, 4, 6, 7, 10], "110": 2, "111": 2, "12": [4, 5, 6, 7], "123e": 7, "13": [4, 6, 7], "1350060": 8, "13560": 8, "139": 2, "14": [4, 7], "15": [4, 7], "150e": 6, "158": 2, "16": [4, 7], "17": [4, 7], "172": [6, 7], "18": [1, 4, 7], "19": [4, 7], "190": 7, "1e": [0, 4, 7], "1e3": 0, "1e6": [0, 7], "1e9": [4, 6, 7, 8], "2": [0, 4, 5, 7, 8], "20": [6, 7], "2000": 5, "200e": 8, "2021": 1, "2022": 2, "20e": 8, "21": 7, "22": 7, "23": 7, "24": 8, "243": 6, "25": 8, "250e": 8, "25e": [6, 7], "25e9": 6, "2e": [0, 7], "2e3": 7, "2e9": 6, "2puls": 7, "3": [0, 2, 4, 5, 7, 8, 11], "300e": [4, 7], "333e6": [4, 7], "339966": 5, "34": [6, 7], "39": [6, 7], "3e": 7, "4": [0, 4, 5, 7, 8], "40": 7, "5": [4, 5, 7, 8], "500": 5, "500000": 7, "500000000": 7, "5014": 0, "517": 2, "57": 7, "5e": [4, 6, 7], "5e5": 7, "5e8": 7, "6": [0, 2, 4, 5, 6, 7, 8], "675": 7, "67e6": 7, "6e": 7, "7": [2, 4, 5, 6, 7, 8, 11], "70000a": 0, "75": [4, 7, 8], "8": [4, 5, 6, 7, 8], "880": 6, "881": 6, "882": 6, "883": 6, "884": 6, "886": 6, "887": 6, "9": [4, 5, 6, 7, 8, 11], "971": 6, "972": 6, "973": 6, "984": 6, "985": 6, "987": 6, "988": 6, "989": 6, "990": 6, "990033": 5, "991": 6, "999999999999999e": 7, "A": [0, 4, 5, 6, 7], "AND": 7, "And": [5, 6, 7, 8], "As": [5, 11], "At": 7, "By": 7, "For": [0, 7, 11], "If": [0, 6, 7, 11], "In": [5, 6, 7, 8, 11], "It": [7, 10], "NO": [4, 7], "NOT": 7, "No": [0, 6], "Not": [0, 7], "ON": [0, 4, 7], "On": 7, "One": 0, "Or": 5, "That": 7, "The": [0, 1, 3, 4, 5, 6, 7, 8, 10, 11], "Then": 5, "There": [1, 11], "These": 7, "To": [7, 11], "Will": 7, "_amplitud": 6, "_awg_fil": 0, "_awgspec": 6, "_data": 4, "_prepareforoutput": 6, "_subelementbuild": 0, "ab": 0, "abl": 11, "about": 0, "abov": [7, 8, 11], "absolut": 7, "accord": [0, 11], "achiev": [7, 8], "action": 2, "activ": [6, 11], "ad": [0, 4, 7, 8], "add": [0, 6, 7], "addarrai": 0, "addblueprint": [0, 4, 6, 7, 8], "addel": [0, 4, 6, 7, 8], "addflag": 0, "addit": 0, "addsubsequ": [0, 8], "adjust": 11, "after": [0, 7], "again": [7, 10, 11], "aim": 10, "all": [0, 7], "allow": [0, 7], "along": 0, "also": [0, 7, 11], "alwai": 0, "ambigu": 0, "amount": 7, "ampkei": 6, "ampl": [0, 4, 5, 7], "amplitud": [0, 4, 6, 7], "an": [0, 5, 6, 7, 8, 11], "anaconda": 11, "ani": [0, 7, 11], "anoth": 7, "anyth": 0, "append": [0, 7], "appli": [0, 5, 6, 7], "applic": 11, "apply_delai": [0, 7], "apply_filt": [0, 7], "applycustomtransferfunct": [0, 5], "applyinversercfilt": [0, 5], "applyrcfilt": [0, 5], "appropri": [0, 4, 7], "appveyor": 2, "ar": [0, 1, 5, 7, 11], "arb": 5, "arb_func": [0, 7], "arbitrari": 0, "arbitrati": 7, "arg": [0, 4, 7], "argslist": 0, "argument": [0, 7], "arrai": [0, 4, 5, 7], "assert": [0, 6], "assign": 0, "associ": [0, 7], "assum": [0, 11], "attach": 4, "august": 2, "auto": 7, "automat": 2, "auxiliari": 2, "averag": 5, "awai": 8, "awg": [0, 6], "awg1": 7, "awg5014": [0, 7], "awg70000": 2, "awg70000a": [0, 6], "awg70002a": 6, "awg_amplitud": 6, "ax": 5, "axi": [0, 7], "azur": 11, "back": [5, 6, 7], "backend": 0, "base": [0, 6, 7, 11], "basebp": 7, "baseel": 0, "baseelem": [6, 7], "baseseq": 7, "baseshap": 6, "bash": 11, "basic": 0, "bb": [4, 6, 7, 8], "bean": 10, "becaus": 5, "becom": [0, 7, 10], "been": [0, 2, 6, 11], "befor": [0, 7, 10, 11], "behind": 3, "being": [4, 7, 11], "below": [0, 7], "besid": 0, "best": 5, "big": 7, "binari": 6, "bind": 0, "bit": [6, 7], "bleak": 7, "blue": [4, 7], "blue_dict": 0, "blueprint": [2, 6, 8, 9, 10], "blueprint_from_descript": 0, "blueprintplott": 2, "bluprint": 1, "bool": [0, 7], "both": [0, 5, 7, 11], "both_chans_awg_input": 7, "bottom": [4, 7], "bound": 0, "box": 4, "bp1": [7, 8], "bp2": [7, 8], "bp3": 8, "bp_atm": 7, "bp_box": [4, 7], "bp_boxes_read": 4, "bp_mod": 7, "bp_one_two": 4, "bp_rtm": [4, 7], "bp_sine": [4, 7], "bp_sineandbox": [4, 7], "bp_squar": [4, 7], "bp_wait": 7, "break": 3, "broad": 10, "broadbean": [4, 5, 6, 7, 8, 10], "broken": 11, "browser": 11, "bsic": 6, "build": [2, 10], "built": [0, 5, 6, 11], "busi": 0, "button": 11, "c": 11, "cabl": 0, "call": [0, 4, 6, 7, 10], "callabl": 0, "can": [0, 6, 7, 8, 11], "capabl": 11, "care": [5, 8, 11], "case": [0, 7, 8], "cd": 11, "cdot": 5, "cell": 6, "centr": 0, "certain": 7, "cfg": 2, "ch1": 6, "chain": 7, "chan": 6, "chan1_awg_input": 7, "chan3_awg_input": 7, "chanc": 11, "chang": [0, 3, 7, 11], "changearg": [0, 6, 7, 8], "changedur": [0, 7], "changelog": 10, "channel": [0, 4, 6, 7], "check": [0, 5, 6, 7, 11], "checkconsist": [0, 7], "choos": 11, "chosen": 8, "chronolog": 7, "chxx_amp": 0, "chxx_offset": 0, "circuit": [0, 5], "class": 0, "classmethod": 0, "clear": 0, "click": 11, "clone": 11, "close": [6, 11], "co": 5, "collect": [0, 4, 7], "color": 5, "com": 7, "come": [4, 7, 8], "command": 11, "compar": 5, "comparison": 2, "compat": 7, "compatipl": 1, "compens": [0, 9, 10], "compil": 6, "compos": 10, "comput": 11, "concern": 0, "conda": [2, 11], "config": 2, "configur": 11, "conflict": 7, "consist": [0, 6, 7], "constant": 0, "construct": [0, 7], "contain": [0, 4, 7], "content": 10, "continu": 6, "convers": 5, "convert": 2, "convolv": 5, "copi": [0, 6, 8], "correct": [0, 6, 7], "correctli": [6, 11], "correspond": 0, "correspondingli": 0, "could": 10, "count": [0, 7], "creat": [4, 7, 10], "current": [0, 7], "curv": 5, "custom": 0, "cut": [5, 7], "cut_off": 0, "cutoff": 0, "d": 8, "data": 0, "dc": [0, 5, 7], "dcgain": [0, 5], "dead": 8, "decid": 11, "decor": 0, "def": [4, 5], "default": [0, 7, 11], "defin": [0, 6], "delai": [0, 4, 6], "delet": 2, "depend": [0, 2, 11], "dependabot": 2, "deprec": 7, "describ": [0, 7, 8], "descript": [0, 4], "design": 7, "desir": 7, "desktop": 11, "detail": 8, "devop": 11, "dict": [0, 7], "dictionari": 0, "differ": [0, 4, 7], "dimensionless": 0, "directli": [4, 7], "discard": 0, "discript": 4, "displai": 7, "disregard": 0, "distorion": 0, "distort": 7, "distribut": 2, "do": [4, 5, 6, 7, 10], "document": [2, 11], "doe": [0, 7, 8, 11], "domain": 5, "don": [0, 7], "done": [5, 6], "down": 6, "download": 11, "drastic": [5, 8], "drive": 6, "driver": [0, 7], "dummi": 0, "dur": [0, 4, 6, 7, 8], "durat": [0, 4, 7], "durslist": 0, "e": [0, 4, 5, 6, 7, 11], "each": [0, 4, 6, 7, 8], "easi": [7, 10], "easili": 7, "edit": 11, "effect": [5, 11], "either": [0, 5, 6, 7, 11], "ekstra": [9, 10], "elem": [4, 6], "elem1": [4, 7, 8], "elem2": [4, 7, 8], "elem3": 8, "elem_read": 4, "element": [1, 2, 6, 8, 9, 10], "element_dict": 0, "element_from_descript": 0, "elementdurationerror": [0, 7], "elemtmp": 4, "els": 0, "enabl": 2, "end": [0, 4, 7], "endpoint": 0, "enough": 7, "ensur": [0, 6, 7, 11], "ensureaverage_fixed_level": 0, "enter": 11, "entir": 8, "enumer": 6, "env": 11, "equival": 7, "error": [0, 7], "especi": 11, "essenti": 7, "etc": [0, 7], "even": 7, "evenli": 0, "event": 0, "event_jump": 0, "event_jump_to": 0, "eventu": [6, 7], "everi": 2, "everyth": 7, "ex": 0, "exact": [0, 7], "exactli": 7, "exampl": [0, 4, 5, 7, 10], "except": 0, "excess": 7, "execut": 11, "exist": 7, "expect": [6, 7], "experi": [5, 7], "experiment": [5, 7], "expon": 0, "export": 0, "ext": 6, "extra": 10, "f": [0, 5, 6, 7], "f0": 6, "f_cut": [0, 5, 7], "facilit": 11, "factor": 10, "fail": 7, "fals": [0, 7], "far": 7, "favorit": 11, "favourit": [5, 10], "featur": [7, 11], "few": [0, 7], "fewer": 8, "ff9900": 5, "fig": 5, "figsiz": [4, 7], "figur": [4, 7], "file": [0, 1, 7, 8, 9, 10, 11], "fill": [0, 4, 7], "filter": [0, 9, 10], "filtertyp": 5, "final": [0, 5, 7], "find": 0, "finish": [0, 7], "first": [0, 5, 6, 7, 8, 11], "fit": 5, "fix": 2, "flag": [0, 2], "float": [0, 4, 7], "follow": [0, 5, 6, 11], "food": 7, "footwork": 6, "forc": 6, "force_triggera": 6, "forg": 0, "forger_kwarg": 0, "form": [0, 2, 4], "format": 7, "frac": 5, "freq": [0, 4, 5, 6, 7], "frequenc": [0, 5, 6, 7, 10], "frequent": 11, "from": [0, 1, 5, 6, 7, 8, 9, 10, 11], "full": [0, 6], "fulli": 0, "fullseq": 8, "func": [0, 5], "function": [0, 2, 4, 6, 7, 9, 10], "funlist": 0, "furthermor": 7, "g": [0, 5, 7], "gain": [0, 5], "gaussian": [0, 7], "gaussian1": [0, 7], "gaussian2": [0, 7], "gaussian_smooth_cutoff": 0, "gener": [0, 4, 6, 7, 10], "get": [0, 5, 7, 8, 10], "getarrai": 0, "getsiscalingandprefix": 0, "gimm": 10, "git": 11, "github": [2, 7, 11], "give": [5, 7], "given": [0, 5], "go": [4, 7], "go_to": [0, 6], "goal": 7, "goe": 7, "goto": 0, "graph": 7, "great": 2, "grei": 8, "gt": [6, 7], "guid": 11, "h": 0, "h_": 5, "h_n": 5, "ha": [0, 5, 6, 7, 8], "had": 10, "halfwai": [4, 7], "han": 5, "hand": [5, 7], "handl": 0, "happen": 0, "harddriv": 6, "have": [0, 2, 4, 5, 6, 7, 10, 11], "heavi": [5, 8], "heavili": 0, "height": [0, 8], "help": [5, 7], "henc": 11, "here": [4, 7, 8, 11], "high": [0, 5, 6, 7], "high_level": 6, "highest": 7, "highli": 11, "hight": 0, "hold": 0, "hole": [4, 7], "hook": 2, "horizont": 7, "hostedtoolcach": [6, 7], "how": [4, 6, 7], "hp": [0, 5, 7], "http": 7, "hz": [0, 5, 6], "i": [0, 2, 4, 5, 6, 7, 8, 10, 11], "id": 11, "ident": [0, 2], "ignor": 0, "imagin": 8, "imaginari": 0, "implement": 7, "import": [4, 5, 6, 7, 8], "improv": 3, "includ": [0, 1, 2, 7, 11], "includetim": 0, "inconsist": 0, "increas": 0, "increment": 0, "inde": 0, "index": [0, 6, 7, 10], "infinit": 0, "info": 0, "inform": [0, 6, 7], "infrastructur": 2, "init_from_json": [0, 4], "initialis": 7, "inlin": 7, "input": [0, 5, 6, 7], "insert": [0, 7], "insertseg": [0, 4, 6, 7, 8], "insid": 7, "inspect": [5, 7], "inst0": [6, 7], "instal": [7, 10], "instanc": [0, 7], "instead": [0, 5, 8], "instr": [6, 7], "instruct": [7, 11], "instrument": [0, 6], "instrument_driv": [6, 7], "int": [0, 4, 5, 7], "integ": [0, 7], "integr": 11, "intend": 0, "intern": [0, 6], "interv": 0, "introduct": [9, 10], "invalid": [2, 7], "invalidforgedsequenceerror": 0, "invers": [0, 5], "invert": [0, 5], "invit": 5, "ipython": 11, "item": 0, "iter": [0, 7], "its": [0, 4, 5, 7, 10], "itself": [0, 11], "januari": 1, "job": 2, "json": [0, 1, 9, 10], "jump": 0, "jump_input": 0, "jump_target": 0, "jupyt": 11, "just": [0, 7, 11], "kei": [0, 6], "keyerror": [0, 6], "keyword": 7, "kill": 0, "kind": 0, "know": 6, "kwarg": 0, "l": 0, "l_n": 5, "label": 5, "lambda": [0, 7], "larg": 8, "last": [0, 6, 7], "later": 6, "launch": 11, "least": 7, "left": [5, 11], "legend": [5, 7], "len": 6, "length": [0, 7], "length_seg": [0, 7], "length_sequenceel": 0, "less": 0, "let": [5, 7, 8, 10], "level": [6, 7], "lib": [6, 7], "librari": 11, "licens": 1, "like": [0, 8], "line": [0, 5, 6, 7, 8, 11], "linearli": 0, "liner": 6, "link": 2, "linspac": [5, 6], "list": [0, 7], "littl": 10, "loadseqxfil": 6, "logic": 10, "long": 8, "longer": 7, "look": [6, 7], "low": [0, 5, 7, 8], "lowest": 0, "lp": [0, 5], "lt": 7, "m": [0, 10], "m1": 0, "m2": 0, "m3": 0, "made": [0, 1, 5], "mai": [0, 5, 7, 11], "mainseq": 8, "make": [0, 4, 5, 7, 8, 11], "make_": 0, "make_awg_fil": 0, "make_send_and_load_awg_fil": 7, "makelinearlyvaryingsequ": 0, "makemeanfit": 7, "makeseqxfil": [0, 6], "makevaryingsequ": [0, 7], "manag": 11, "mandatori": 0, "mani": [0, 2], "manipul": 10, "mark": 2, "marked_for_delet": 0, "marker": [0, 4, 6], "marker1": [0, 7], "marker2": [0, 7], "markerid": [0, 4, 7], "match": [0, 6, 7], "mathcal": 5, "mathrm": 5, "matplotlib": [4, 5, 6, 7, 8], "max": [0, 5, 8], "md": 2, "mean": [0, 5, 6, 7], "measur": 5, "memori": [6, 8], "mention": 11, "method": [0, 2, 7, 11], "mhz": 7, "middl": 0, "mimick": 7, "min": [0, 8], "miniconda": 11, "minimum": [10, 11], "minmax": 0, "missingfrequencieserror": 0, "mix": 11, "mode": [0, 5, 7], "model": 5, "modern": 2, "modifi": 0, "modul": [4, 6, 7, 9, 10], "modulenotfounderror": 6, "monoton": 0, "more": [0, 6, 7, 8, 11], "most": 6, "move": 2, "mpl": [4, 7], "mu": 0, "much": 8, "mulitpli": 5, "must": [0, 4, 6, 7], "my": 10, "myfunc": 7, "mysin": [4, 7], "n": [0, 4, 5, 7, 11], "name": [0, 4, 5, 6, 7, 8, 10, 11], "nameerror": 6, "namelist": 0, "navig": 11, "nb": [6, 7], "nbagg": 4, "ndarrai": 0, "necessari": 0, "need": [0, 4, 5, 6, 11], "neg": 0, "neither": 0, "nest": 0, "never": 7, "new": [0, 3, 7, 11], "new_valu": 7, "newdur": 0, "newpackag": 7, "newseq": 7, "next": [0, 4, 6, 7, 11], "nice": 5, "nois": 5, "non": 0, "none": [0, 4, 7], "nor": 0, "normal": 0, "note": [0, 4, 5, 7, 11], "notebook": [5, 6, 8, 11], "noth": 7, "now": [2, 6, 7, 10], "np": [0, 5, 6, 7], "npoint": 0, "npt": [0, 5], "nrep": 0, "nth": 5, "number": [0, 7, 8], "numpi": [0, 1, 4, 5, 6, 7], "obj_to_plot": 0, "object": [0, 6, 7], "occur": 7, "off": [0, 4, 5, 6, 7], "offici": 11, "offset": [0, 7], "ok": 7, "old": 11, "omit": 7, "onc": [7, 11], "one": [0, 5, 6, 7, 10], "onli": [0, 4, 7, 8], "open": 11, "opt": [6, 7], "option": [0, 7, 8, 11], "order": [0, 5, 7], "origin": [0, 5], "other": [7, 11], "otherwis": 0, "ouput": 0, "our": 5, "out": [0, 8, 10], "output": [0, 2, 8], "outputforawgfil": [0, 7], "outputforseqxfil": [0, 6], "outputforseqxfilewithflag": [0, 2], "over": [4, 5, 7], "overal": 7, "overlap": 7, "overview": 7, "overwrit": 0, "overwritten": [0, 7], "own": 10, "p": 0, "packag": [1, 2, 6, 7, 10, 11], "page": 10, "pain": 0, "paramet": [0, 5], "part": [0, 4, 6, 7], "particular": 10, "pass": [0, 5, 7], "path": [0, 4, 11], "path_to_fil": 0, "peak": [0, 4, 6, 7], "pep516": 2, "perform": [5, 7, 10], "period": [5, 8, 11], "perturb": 8, "phase": [0, 4, 7], "physic": [0, 7], "pi": [5, 7], "pin": 2, "pip": [2, 11], "pkg": 0, "place": 0, "plai": [0, 8, 11], "plain": 11, "plateau": 7, "pleas": [0, 7, 11], "plot": [2, 4, 5, 6, 7, 8, 10], "plotawgoutput": 2, "plotel": 2, "plotsequ": 2, "plotter": [0, 4, 6, 7, 8], "plt": 5, "po": [0, 4, 7], "point": [0, 6, 7, 8], "posit": [0, 4, 7], "poss": [0, 7], "possibl": 5, "possibli": 0, "pre": [0, 5], "precommit": 2, "prefer": 11, "prefix": 0, "prepend": 0, "present": 11, "pretend": 5, "pretti": 0, "previou": [0, 7], "previous": 0, "principl": 0, "print": [0, 4, 7], "prior": 0, "proce": [4, 7], "produc": 11, "prompt": 11, "properti": 0, "protect": 7, "provid": [0, 5, 7], "publish": 11, "pull": 11, "puls": [0, 7, 8, 10], "pulseatom": [0, 4, 6, 7, 8], "pulsebp": 7, "pulsebuild": [4, 9, 10], "pulsepi": 7, "put": [7, 8], "py": [6, 7], "pypi": 2, "pyplot": 5, "pyproject": 2, "pytest": 11, "python": [0, 2, 6, 7, 11], "python3": [6, 7], "qcode": [0, 6, 7, 11], "question": 0, "quit": 11, "rais": [0, 6, 7], "ramp": [0, 4, 6, 7, 8, 10], "ramp2": 7, "ramp_tim": 6, "randn": 5, "random": 5, "rang": [0, 5, 7, 8], "rate": [0, 4, 5, 6, 7], "rather": 0, "rc": [0, 9, 10], "rcparam": [4, 7], "reach": 7, "read": [0, 1, 8, 9, 10], "readback": 4, "readblueprint": 4, "readm": 2, "readout": 8, "real": [0, 5, 7], "realist": 5, "receiv": 0, "recent": 6, "recommend": 11, "recov": 5, "red": 7, "reduc": [8, 10], "regular": 2, "regularli": 11, "reinspect": 7, "reinstal": 11, "rel": [0, 7], "releas": [1, 2], "remov": [0, 2, 7], "removeseg": [0, 7], "removesegmentmark": 0, "repeat": [0, 4, 7, 8], "repeatandvarysequ": [0, 7], "repetit": 0, "replac": [0, 2, 7], "replaced_bi": 0, "replaceeverywher": [0, 7], "repositori": 11, "repres": [0, 7], "requir": [0, 2, 7, 10], "resid": 7, "resolut": 6, "respect": 7, "respons": 7, "rest": 7, "result": [5, 8], "retriev": 7, "return": [0, 4, 5, 7], "right": 5, "ripasso": [9, 10], "round": 0, "run": [0, 11], "s_": 5, "sa": [0, 7], "sai": [7, 8, 10], "said": 0, "same": [0, 5, 7, 8, 10, 11], "sampl": [0, 4, 5, 6, 7], "sample_r": 6, "sane": 5, "saniti": [5, 7], "scale": 0, "scene": 3, "screen": 11, "sdist": 2, "search": 10, "second": [0, 6, 7, 11], "section": [7, 11], "see": [5, 7, 11], "segment": [0, 4], "segmentdurationerror": 0, "segmentmarker1": 0, "segmentmarker2": 0, "select": 11, "self": [6, 11], "semi": 5, "sendseqxfil": 6, "sens": 7, "sensibl": 7, "seq": [0, 4, 6, 7, 8], "seq1": [4, 7], "seq2": [7, 8], "seq3": 8, "seq_dict": 0, "seqamp": 4, "seqlen": 6, "seqnam": [0, 6], "seqoffset": 4, "seqtmp": 4, "sequenc": [1, 2, 8, 9, 10], "sequence_from_descript": 0, "sequencecompatibilityerror": 0, "sequenceconsistencyerror": 0, "sequencingerror": 0, "sequens": 0, "seqx": 0, "seqx_input": 6, "seqx_output": 6, "seri": 6, "set": [0, 2, 4, 6, 7, 8, 10], "set_titl": 5, "set_xlabel": 5, "set_ylabel": 5, "setchannelamplitud": [0, 4, 6, 7], "setchanneldelai": [0, 7], "setchannelfiltercompens": [0, 7], "setchanneloffset": [0, 4, 7], "setchannelvoltagerang": 0, "setsegmentmark": [0, 4, 6, 7], "setsequenceset": [0, 7], "setsequencetrack": 6, "setsequencingeventinput": 0, "setsequencingeventjumptarget": [0, 4, 7], "setsequencinggoto": [0, 4, 6, 7], "setsequencingnumberofrepetit": [0, 4, 7, 8], "setsequencingtriggerwait": [0, 4, 6, 7], "setsequencingxxx": 7, "setsr": [0, 4, 6, 7, 8], "settl": 7, "setup": 2, "sever": [0, 7], "shape": 0, "shell": 11, "short": 0, "should": [0, 5, 7, 11], "show": [7, 8], "showprint": [0, 7], "shut": 6, "sig": 5, "sig_point": 5, "sigma": 0, "signal": [0, 5, 7], "signal1": 5, "signal1_check1": 5, "signal1_check2": 5, "signal1_comp": 5, "signal1_filt": 5, "signal2": 5, "signal2_check1": 5, "signal2_check2": 5, "signal2_comp": 5, "signal2_filt": 5, "signal_compens": 5, "signal_filt": 5, "signatur": [0, 4, 6, 7], "similarli": 5, "simpl": 0, "simpli": 5, "simultan": 0, "sin": [0, 5], "sinc": [6, 7], "sine": [0, 4, 6, 7, 8, 10], "sine_amp": 6, "sine_freq": 6, "sinebp": 7, "singl": [0, 7, 11], "sit": [0, 7], "site": [6, 7], "situat": 5, "size": [0, 8], "slice": 7, "sliceabl": [0, 7], "slope": 7, "small": [4, 7], "smaller": 8, "smooth": [0, 5], "so": [0, 4, 6, 7, 10, 11], "some": [0, 5, 7, 8, 11], "someth": [0, 7], "sometim": 5, "sourc": 0, "space": 5, "spec": 0, "special": 0, "specif": [0, 2, 7, 10], "specifi": [0, 5, 6, 7], "specificationinconsistencyerror": 0, "spyder": 11, "squar": [5, 7, 10], "squarewav": 5, "sr": [0, 4, 5, 6, 7, 8], "stabl": 11, "start": [0, 4, 7, 8, 10], "state": [0, 6], "static": 0, "step": [0, 7], "sthe": 7, "stick": 7, "still": [0, 7, 8], "stop": [0, 4, 6, 7, 8], "str": [0, 4, 7], "straighforward": 7, "string": [0, 7], "structur": 0, "submodul": [2, 10], "subplot": [4, 5, 7], "subsequ": [0, 9, 10], "suppli": [9, 10], "support": [0, 1, 2], "sure": [6, 11], "switch": 7, "switch_on_tim": 7, "system": [5, 8], "t": [0, 5, 7], "t1": [6, 8], "t2": [6, 8], "t3": 8, "t_sine": 6, "tab": 11, "take": [0, 7, 11], "taken": 5, "target": 0, "tau": [0, 5, 7], "tcpip0": [6, 7], "tektronix": [2, 6], "tektronix_awg5014": 7, "tempseq": 4, "termin": 11, "test": [2, 11], "testdata": 4, "text": 5, "tf_amp": [0, 5], "tf_freq": [0, 5], "tf_noise_amp": 5, "tf_point": 5, "than": [0, 7], "thei": [0, 7], "them": [0, 2, 5, 11], "therefor": [0, 10], "thi": [0, 1, 2, 5, 6, 7, 8, 11], "thing": [5, 7, 10], "though": 0, "through": [0, 4, 7, 11], "throughout": 7, "thu": [7, 8], "tight_layout": 5, "time": [0, 5, 6, 7, 8], "timeout": 7, "timestep": 7, "toc": 7, "togeth": [0, 7, 8], "toggl": 0, "toml": 2, "too": [0, 7], "tool": 10, "top": [4, 7], "top2": 7, "total": [0, 7], "traceback": 6, "track": [6, 11], "transfer": [0, 6, 9, 10], "transform": [0, 5], "translat": 7, "transmiss": [5, 7], "travi": 2, "trig_wait": 0, "triga": [0, 6], "trigb": [0, 6], "trigger": [0, 6], "true": [0, 4, 5, 7], "tupl": [0, 6, 7], "turn": [0, 6, 7], "tutori": [6, 9, 10], "tutorial_sequ": 6, "twice": [4, 7, 10], "two": [0, 5, 6, 7], "txt": 2, "type": [0, 5], "u": 5, "un": 5, "uncompres": 8, "undesir": 0, "uninstal": 11, "union": 0, "uniqu": 7, "unit": [0, 7], "unless": 0, "unphys": 5, "unsign": 7, "unsurprisingli": 7, "until": 7, "up": [4, 6, 7], "updat": [2, 6], "upgrad": 11, "upload": [0, 2, 7], "upon": 11, "us": [0, 2, 6, 7, 10], "user": [0, 7, 9, 10, 11], "userwarn": 7, "usual": 7, "v": [0, 6], "v3": 11, "valid": [0, 4, 7], "validatedur": [0, 7], "valu": [0, 5, 7], "valueerror": [0, 7], "vari": [0, 6, 8], "variat": [0, 8], "varym": 7, "verbos": 0, "veri": [5, 7, 8], "version": [1, 2, 7, 11], "vertic": 7, "via": [0, 7, 8, 11], "visualis": 5, "vocabulari": 7, "volt": 7, "voltag": [0, 6, 7, 8], "vscode": 11, "wa": [0, 5, 7], "wai": [6, 7], "wait": [0, 6, 7, 8, 10], "waituntil": [0, 7], "want": [0, 6, 8, 11], "warn": 7, "wave": [0, 5, 10], "waveform": [0, 6, 7, 8], "we": [0, 4, 5, 6, 7, 8, 11], "welcom": 10, "well": [0, 7], "went": 0, "wether": 0, "wfm": [0, 6], "what": [0, 6], "whatev": 0, "wheel": 2, "when": [0, 5, 7, 8, 11], "where": [0, 7, 8], "wherea": 7, "whether": [0, 11], "which": [0, 7, 11], "wide": 8, "window": [2, 11], "without": 7, "work": [4, 7, 11], "wrap": [4, 7], "wrapper": 7, "write": [0, 1, 9, 10], "write_to_json": [0, 4], "writeblueprint": 4, "writen": 0, "wrong": [0, 7], "x64": [6, 7], "yet": 7, "yield": 7, "you": [0, 7, 11], "yourself": 11, "zero": [0, 5, 7]}, "titles": ["broadbean package", "Changelog for broadbean 0.10.0", "Changelog for broadbean 0.11.0", "Changelogs", "Read and Write from JSON file Tutorial", "Filter compensation with the ripasso module", "Introduction", "Pulsebuilding tutorial", "Subsequences", "Broadbean Examples", "Documentation", "Getting Started"], "titleterms": {"0": [1, 2], "1": [6, 8], "10": 1, "11": 2, "2": 6, "3": 6, "4": 6, "5": 6, "5014": 7, "The": 2, "ar": 4, "assign": 6, "awg": 7, "back": 4, "basic": 7, "behind": 2, "blueprint": [0, 4, 7], "break": [1, 2], "broadbean": [0, 1, 2, 9, 11], "build": [4, 6], "chang": [1, 2], "changelog": [1, 2, 3], "compens": [5, 7], "compress": 8, "content": [0, 7], "creat": 11, "date": 11, "delai": 7, "develop": 11, "document": 10, "driver": 6, "ekstra": 4, "element": [0, 4, 7], "environ": 11, "exampl": [8, 9], "file": [4, 6], "filter": [5, 7], "from": 4, "function": 5, "get": 11, "ident": 4, "improv": 2, "indic": 10, "initialis": 6, "instal": 11, "intro": 7, "introduct": 6, "json": 4, "keep": 11, "latest": 11, "lingo": 7, "load": 6, "make": 6, "marker": 7, "modifi": 7, "modul": [0, 5], "new": [1, 2], "normal": 7, "now": 8, "output": [6, 7], "packag": 0, "paramet": 7, "plai": 6, "plot": 0, "pulsebuild": 7, "rc": 5, "read": 4, "releas": 11, "requir": 11, "ripasso": [0, 5], "scene": 2, "segment": 7, "send": 6, "sequenc": [0, 4, 6, 7], "seqx": 6, "set": 11, "special": 7, "start": 11, "step": 6, "submodul": 0, "subsequ": 8, "suppli": 5, "tabl": [7, 10], "tektronix": 7, "test": 4, "tool": 0, "transfer": 5, "tutori": [4, 7], "uncompress": 8, "up": 11, "updat": 11, "us": [8, 11], "user": 5, "vari": 7, "write": 4, "your": 11}}) \ No newline at end of file diff --git a/start/index.html b/start/index.html new file mode 100644 index 000000000..c59caa924 --- /dev/null +++ b/start/index.html @@ -0,0 +1,461 @@ + + + + + + + + + Getting Started - broadbean 0.15.0.dev699 documentation + + + + + + + + + + + + + + + + Contents + + + + + + Menu + + + + + + + + Expand + + + + + + Light mode + + + + + + + + + + + + + + Dark mode + + + + + + + Auto light/dark, in light mode + + + + + + + + + + + + + + + Auto light/dark, in dark mode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +Skip to content + + + +
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+
+
+ + + + + Back to top + +
+ +
+ +
+ +
+
+
+

Getting Started

+
+
+
+

Requirements

+

You need a working python 3.7 installation, as the minimum Python version (at present, +we recommend python 3.10), to be able to use broadbean. We highly recommend installing +Miniconda, which takes care of installing Python and managing packages. In the following +it will be assumed that you use Miniconda. Download and install it from here. +Make sure to download the latest version with python 3.10.

+

Once you download, install Miniconda according to the instructions on screen, +choosing the single user installation option.

+

The next section will guide you through the installation of broadbean on Windows.

+
+
+

Installation

+

Before you install broadbean you have to decide whether you want to install the +latest stable published release or if you want to get the latest developer version +from broadbean repository on Azure Devops. To install the official package you will need to +configure your computer

+
+

Create a broadbean environment

+

As mentioned above, it is recommended to use broadbean from a conda environment. +We create that by executing the following command:

+
conda create -n broadbean-env python=3.10
+
+
+

This will create a python 3.9 environment named broadbean-env. +Once the environment is created close your shell and open it again to ensure that changes take effect.

+
+
+

Installing the latest broadbean release

+

The broadbean package can be installed using pip:

+
conda activate broadbean-env
+pip install broadbean
+
+
+
+
+

Setting up your development environment

+

The default broadbean installation does not include packages such as pytest that are required for testing and development. +For development and testing, install broadbean with the test feature. +Note that broadbean requires python v3.7, so be sure to include that option when creating your new development environment. +If you run broadbean from Jupyter notebooks, you may also need to install jupyter into the development environment.

+
conda create -n broadbean-development python=3.10
+conda activate broadbean-development
+cd <path to broadbean repository>
+pip install -e .[test]
+
+
+
+
+

Updating Broadbean

+

If you have installed broadbean with pip, run the following to update:

+
pip install --upgrade broadbean
+
+
+

Updates to broadbean are quite frequent, so please check regularly.

+

If you have installed broadbean from the cloned git repository, pull the broadbean +repository using your favorite method (git bash, git shell, github desktop, …).

+
+
+

Keeping your environment up to date

+

Dependencies are periodically been adjusted for broadbean (and for qcodes, which broadbean is built upon) +and new versions of packages that broadbean depends on get released. +Conda/Miniconda itself is also being updated.

+

Hence, to keep the broadbean conda environment up to date, please run for the activated broadbean environment:

+
conda update -n base conda -c defaults
+conda env update
+
+
+

The first line ensures that the conda package manager it self is +up to date, and the second line will ensure that the latest versions of the +packages used by broadbean are installed. See +here for more +documentation on conda env update.

+

If you are using broadbean from an editable install, you should also reinstall broadbean +before upgrading the environment to make sure that dependencies are tracked correctly using:

+
pip uninstall broadbean
+pip install -e <path-to-repository>
+
+
+

Note that if you install packages yourself into the same +environment it is preferable to install them using conda. There is a chance that +mixing packages from conda and pip will produce a broken environment, +especially if the same package is installed using both pip and conda.

+
+
+
+

Using broadbean

+

For using broadbean, as with any other python library, it is useful to use an +application that facilitates the editing and execution of python files. Some +options are:

+
+
    +
  • Jupyter, a browser based notebook

  • +
  • Vscode, IDE with ipython capabilities

  • +
  • Spyder, an integrated development environment

  • +
+
+

For other options you can launch a terminal either via the Anaconda Navigator +by selecting broadbean in the Environments tab and left-clicking on the play +button or by entering

+
activate broadbean
+
+
+

in the Anaconda prompt.

+

From the terminal you can then start any other application, such as IPython or +just plain old Python.

+
+
+ +
+
+ +
+ +
+
+ + + + + + \ No newline at end of file