Esempio n. 1
0
 def __init__(self, strPort=""):
     self.port = ebb_serial.openPort(strPort)
     if self.port == None:
         print("Try to open port again")
         self.port = ebb_serial.openPort("")
     if self.port != None:
         print("Comport opened...")
     self.sleeptime_pen = 0.1
     self.speedlimit = float(12000) / float(1000)  # 20000 steps/500ms
Esempio n. 2
0
	def effect( self ):
		'''Main entry point: check to see which tab is selected, and act accordingly.'''

		self.svg = self.document.getroot()
		self.CheckSVGforEggbotData()

 		if ((self.options.tab == '"Help"') or (self.options.tab == '"options"')  or (self.options.tab == '"timing"')):
 			pass
 		else:
 			self.serialPort = ebb_serial.openPort()
 			if self.serialPort is None:
				inkex.errormsg( gettext.gettext( "Failed to connect to EggBot. :(" ) )

			if self.options.tab == '"splash"':
				self.allLayers = True
				self.plotCurrentLayer = True
				self.svgNodeCount = 0
				self.svgLastPath = 0
				unused_button = ebb_motion.QueryPRGButton(self.serialPort)	#Query if button pressed
				self.svgLayer = 12345;  # indicate that we are plotting all layers.
				self.plotToEggBot()
	
			elif self.options.tab == '"resume"':
				unused_button = ebb_motion.QueryPRGButton(self.serialPort)	#Query if button pressed
				self.resumePlotSetup()
				if self.resumeMode:
					self.plotToEggBot()
				elif ( self.options.cancelOnly ):
					pass
				else:
					inkex.errormsg( gettext.gettext( "Truly sorry, there does not seem to be any in-progress plot to resume." ) )
	
			elif self.options.tab == '"layers"':
				self.allLayers = False
				self.plotCurrentLayer = False
				self.LayersPlotted = 0
				self.svgLastPath = 0
				unused_button = ebb_motion.QueryPRGButton(self.serialPort)	#Query if button pressed
				self.svgNodeCount = 0;
				self.svgLayer = self.options.layernumber
				self.plotToEggBot()
				if ( self.LayersPlotted == 0 ):
					inkex.errormsg( gettext.gettext( "Truly sorry, but I did not find any numbered layers to plot." ) )
					
			elif self.options.tab == '"setup"':
				self.setupCommand()	
			elif self.options.tab == '"manual"':
				self.manualCommand()
				
			if self.serialPort is not None:
				ebb_motion.doTimedPause(self.serialPort, 10) #Pause a moment for underway commands to finish...
				ebb_serial.closePort(self.serialPort)	
				
		self.svgDataRead = False
		self.UpdateSVGEggbotData( self.svg )
		return
Esempio n. 3
0
	def effect( self ):
		'''Main entry point: check to see which tab is selected, and act accordingly.'''

		self.svg = self.document.getroot()
		self.CheckSVGforEggbotData()

 		if ((self.options.tab == '"Help"') or (self.options.tab == '"options"')  or (self.options.tab == '"timing"')):
 			pass
 		else:
 			self.serialPort = ebb_serial.openPort()
 			if self.serialPort is None:
				inkex.errormsg( gettext.gettext( "Failed to connect to EggBot. :(" ) )

			if self.options.tab == '"splash"':
				self.allLayers = True
				self.plotCurrentLayer = True
				self.svgNodeCount = 0
				self.svgLastPath = 0
				unused_button = ebb_motion.QueryPRGButton(self.serialPort)	#Query if button pressed
				self.svgLayer = 12345;  # indicate that we are plotting all layers.
				self.plotToEggBot()
	
			elif self.options.tab == '"resume"':
				unused_button = ebb_motion.QueryPRGButton(self.serialPort)	#Query if button pressed
				self.resumePlotSetup()
				if self.resumeMode:
					self.plotToEggBot()
				elif ( self.options.cancelOnly ):
					pass
				else:
					inkex.errormsg( gettext.gettext( "Truly sorry, there does not seem to be any in-progress plot to resume." ) )
	
			elif self.options.tab == '"layers"':
				self.allLayers = False
				self.plotCurrentLayer = False
				self.LayersPlotted = 0
				self.svgLastPath = 0
				unused_button = ebb_motion.QueryPRGButton(self.serialPort)	#Query if button pressed
				self.svgNodeCount = 0;
				self.svgLayer = self.options.layernumber
				self.plotToEggBot()
				if ( self.LayersPlotted == 0 ):
					inkex.errormsg( gettext.gettext( "Truly sorry, but I did not find any numbered layers to plot." ) )
					
			elif self.options.tab == '"setup"':
				self.setupCommand()	
			elif self.options.tab == '"manual"':
				self.manualCommand()
				
			if self.serialPort is not None:
				ebb_motion.doTimedPause(self.serialPort, 10) #Pause a moment for underway commands to finish...
				ebb_serial.closePort(self.serialPort)	
				
		self.svgDataRead = False
		self.UpdateSVGEggbotData( self.svg )
		return
Esempio n. 4
0
            save_img(r, c)
            # c0->c99 requires only 98 movements
            if c != cols - 1:
                ebb_motion.doABMove(port, 0, STEPS_X, duration_x)
                time.sleep(duration_x / 1000)

        # move back to the first column
        ebb_motion.doABMove(port, 0, -1 * STEPS_X * (cols - 1),
                            return_duration_x)
        time.sleep(return_duration_x / 1000)
        if r != rows - 1:
            ebb_motion.doABMove(port, STEPS_Y, 0, duration_y)
            time.sleep(duration_y / 1000)

    # move back to home position (0, 0)
    ebb_motion.doABMove(port, -1 * STEPS_Y * (rows - 1), 0, return_duration_y)
    time.sleep(return_duration_y / 1000)


if __name__ == "__main__":
    duration = 200

    connect()
    port = ebb_serial.openPort()
    ebb_motion.sendDisableMotors(port)

    move_over_page(port, COLUMNS, ROWS, duration)

    ebb_motion.sendDisableMotors(port)
    sock_image.close()
Esempio n. 5
0
 def __init__(self):
     self.port = ebb_serial.openPort()
     self.sleeptime_pen = 0.15
     self.speedlimit = float(15000) / float(1000)  # 20000 steps/500ms
Esempio n. 6
0
    def effect(self):
        '''Main entry point: check to see which mode/tab is selected, and act accordingly.'''

        self.versionString = "AxiDraw Merge - Version 2.0.0 dated 2018-07-10"
        self.spewDebugdata = False

        self.start_time = time.time()
        self.pageDelays = 0.0
        self.rows_plotted = 0

        self.serial_port = None
        self.svg_data_written = False
        self.csv_data_read = False
        self.csv_data_written = False
        self.csv_file_path = None
        self.csv_row_count = None

        self.delay_between_rows = False  # Not currently delaying between copies
        self.b_stopped = False  # Not currently stopped by button press

        #Values to be read from file:
        self.svg_rand_seed_Old = float(1.0)
        self.svg_row_old = float(0.0)  # Last row plotted.
        self.svg_rand_seed = float(1.0)
        self.svgRow = int(1)

        self.row_to_plot = 1

        skipSerial = False
        if self.options.preview:
            skipSerial = True

        # Input sanitization:
        self.options.mode = self.options.mode.strip("\"")
        self.options.single_type = self.options.single_type.strip("\"")
        self.options.data_action = self.options.data_action.strip("\"")
        self.options.fontface = self.options.fontface.strip("\"")
        self.options.setup_type = self.options.setup_type.strip("\"")
        self.options.resume_type = self.options.resume_type.strip("\"")

        if (self.options.page_delay < 0):
            self.options.page_delay = 0

        if (self.options.mode == "model"):
            return
        if (self.options.mode == "options"):
            return
        if (self.options.mode == "timing"):
            return
        if (self.options.mode == "csv"):
            skipSerial = True
        if (self.options.mode == "text"):
            return
        if (self.options.mode == "version"):
            #             inkex.errormsg( gettext.gettext(self.versionString)) # Accessible from CLI only
            return

        import axidraw  # https://github.com/evil-mad/axidraw
        import hershey_advanced

        ad = axidraw.AxiDraw()
        hta = hershey_advanced.HersheyAdv()

        ad.getoptions([])
        self.svg = self.document.getroot()
        ad.ReadWCBdata(self.svg)
        self.svg_row_old = ad.svg_row_old  # Access params from ReadWCBdata
        ad.called_externally = True

        if skipSerial == False:
            self.serial_port = ebb_serial.openPort()
            if self.serial_port is None:
                inkex.errormsg(
                    gettext.gettext("Failed to connect to AxiDraw. :("))
                return

        if self.options.mode == "autoPlot":

            # Note: In preview mode, we only preview-plot the _last_ row to be plotted.

            pen_down_travel_inches = 0.0  # Local variable
            pen_up_travel_inches = 0.0  # Local variable
            pt_estimate = 0.0  # Local variable
            continue_plotting = True

            self.row_to_plot = int(self.options.first_row)

            if (self.options.last_row == 0
                ):  # "Continue until last row of data"
                self.options.last_row = 10000  # A large number; only limit by size of data.

            self.ReadCSV()
            if (self.csv_row_count is not None):

                if (self.row_to_plot > self.csv_row_count):
                    inkex.errormsg(
                        gettext.gettext(
                            "No merge data found in specified range of rows."))
                    continue_plotting = False

                if (self.options.last_row < self.options.first_row):
                    continue_plotting = False
                    inkex.errormsg('Nothing to plot; No data rows selected.')

                if (continue_plotting):
                    ad.backup_original = copy.deepcopy(self.original_document)

                    while (continue_plotting):
                        self.svg_rand_seed = round(
                            time.time() *
                            100) / 100  # New random seed for new plot
                        self.mergeAndPlot(hta, ad)

                        if self.spewDebugdata:
                            inkex.errormsg('Merging row number ' +
                                           str(int(self.row_to_plot)) + '.')

                        pen_down_travel_inches = pen_down_travel_inches + ad.pen_down_travel_inches  # Local copy
                        pen_up_travel_inches = pen_up_travel_inches + ad.pen_up_travel_inches  # Local copy
                        pt_estimate = pt_estimate + ad.pt_estimate  # Local copy

                        if (
                                ad.b_stopped
                        ):  # A pause was triggered while plotting the previous row.
                            inkex.errormsg(
                                'Paused while plotting row number ' +
                                str(int(self.row_to_plot)) + '.')
                            continue_plotting = False
                        else:  # Finished plotting the row without being paused

                            self.row_to_plot = self.row_to_plot + 1

                            if (self.row_to_plot > self.options.last_row):
                                continue_plotting = False  # We have already finished the last row.
                            else:  # We will be plotting at least one more row. Delay first.
                                self.next_csv_row()
                                self.delay_between_rows = True  # Indicate that we are currently delaying between copies
                                timeCounter = 10 * self.options.page_delay  # 100 ms units
                                if self.spewDebugdata:
                                    inkex.errormsg(
                                        'Delaying ' +
                                        str(int(self.options.page_delay)) +
                                        ' seconds.')
                                while (timeCounter > 0):
                                    timeCounter = timeCounter - 1
                                    if (self.b_stopped == False):
                                        if self.options.preview:
                                            pt_estimate += 100
                                            self.pageDelays += 0.1
                                        else:
                                            time.sleep(
                                                0.100
                                            )  # Use short intervals to improve responsiveness
                                            self.PauseCheck(
                                            )  #Query if button pressed
                                self.delay_between_rows = False  # Not currently delaying between copies
                                if (self.b_stopped == True
                                    ):  # if button pressed
                                    self.row_to_plot = self.row_to_plot - 1  # Backtrack; we didn't actually get to that row.
                                    inkex.errormsg(
                                        'Sequence halted after row number ' +
                                        str(int(self.row_to_plot)) + '.')
                                    continue_plotting = False  # Cancel plotting sequence

                    ad.pen_down_travel_inches = pen_down_travel_inches  # Copy local values back to ad.(values)
                    ad.pen_up_travel_inches = pen_up_travel_inches  #  for printing time report.
                    ad.pt_estimate = pt_estimate
                    self.printTimeReport(ad)

        elif self.options.mode == "singlePlot":

            doPlot = True

            if (self.options.single_type == "singleFix"
                ):  # Plot a specified row
                self.row_to_plot = int(self.options.single_row)
            elif (self.options.single_type == "singleAdv"
                  ):  # Automatically advance
                self.row_to_plot = int(self.svg_row_old + 1)
            else:  # ( self.options.single_type == "queryRow" )
                # No plotting; Query and report last row plotted
                inkex.errormsg('Last row merged: Row number ' +
                               str(int(self.svg_row_old)))
                inkex.errormsg('Next row to merge: Row number ' +
                               str(int(self.svg_row_old + 1)))
                doPlot = False

            if doPlot:
                self.svg_rand_seed = round(
                    time.time() * 100) / 100  # New random seed for new plot
                self.options.last_row = self.row_to_plot  # Last row is equal to first row, in this case.
                self.ReadCSV()
                if (self.csv_row_count is not None):

                    if (self.row_to_plot > self.csv_row_count):
                        inkex.errormsg(
                            gettext.gettext(
                                "No merge data found in row number ") +
                            str(self.row_to_plot) + '.')
                    else:
                        ad.backup_original = copy.deepcopy(
                            self.original_document)
                        self.mergeAndPlot(hta, ad)
                        self.printTimeReport(ad)

        elif self.options.mode == "resume":

            ad.options.mode = "resume"
            self.svg_rand_seed = ad.svg_rand_seed_old  # Preserve random seed
            self.row_to_plot = self.svg_row_old  # Preserve SVG Row
            ad.options.resume_type = self.options.resume_type

            if (self.options.resume_type == "home"):
                self.options.fontface = "none"  # Disable Hershey Text substitution
                self.mergeAndPlot(hta, ad)
            elif (ad.svg_application_old != "Axidraw Merge"):
                inkex.errormsg(
                    gettext.gettext(
                        "No AxiDraw Merge resume data found in file."))
            elif (ad.svg_layer_old == 12345
                  ):  # There appears to be a paused "all layers" plot
                self.options.last_row = self.row_to_plot
                self.ReadCSV()

                if (self.csv_row_count is not None):

                    ad.backup_original = copy.deepcopy(self.original_document)
                    self.mergeAndPlot(hta, ad)
                    self.printTimeReport(ad)
            else:
                inkex.errormsg(
                    gettext.gettext(
                        "No in-progress plot data found saved in file."))

        elif self.options.mode == "setup":

            if self.options.preview:
                inkex.errormsg(
                    gettext.gettext(
                        'Command unavailable while in preview mode.'))
            else:

                ad.options.mode = self.options.mode
                ad.options.setup_type = self.options.setup_type

                ad.options.pen_pos_up = self.options.pen_pos_up
                ad.options.pen_pos_down = self.options.pen_pos_down
                ad.document = self.document
                ad.options.port = self.serial_port
                ad.effect()

        elif self.options.mode == "csv":
            #  Open file dialog

            if self.options.data_action == "choose":
                # Select and upload a CSV file

                try:
                    import pygtk
                    pygtk.require('2.0')
                    import gtk  # Use gtk to create file selection dialog box.
                    # self.useGTK = True
                except:
                    inkex.errormsg(
                        "Unable to load GTK, a required component. Please contact technical support for assistance."
                    )
                    return

                filename = None

                dialog = gtk.FileChooserDialog(
                    title="Please choose a CSV file",
                    action=gtk.FILE_CHOOSER_ACTION_OPEN,
                    buttons=(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL,
                             gtk.STOCK_OPEN, gtk.RESPONSE_OK))

                dialog.set_default_response(gtk.RESPONSE_OK)

                filter = gtk.FileFilter()
                filter.set_name("Text/CSV")
                filter.add_pattern("*.CSV")
                filter.add_pattern("*.csv")
                filter.add_pattern("*.txt")
                filter.add_pattern("*.TXT")
                filter.add_mime_type("text/csv")
                filter.add_mime_type("text/plain")
                filter.add_mime_type("application/csv")
                filter.add_mime_type("application/x-csv")
                filter.add_mime_type("text/x-csv")
                filter.add_mime_type("text/csv")
                filter.add_mime_type("text/comma-separated-values")
                filter.add_mime_type("text/x-comma-separated-values")
                filter.add_mime_type("text/tab-separated-values")
                dialog.add_filter(filter)
                filter = gtk.FileFilter()
                filter.set_name("All files")
                filter.add_pattern("*")
                dialog.add_filter(filter)

                response = dialog.run()

                if response == gtk.RESPONSE_OK:
                    filename = dialog.get_filename()
                    #inkex.errormsg( "File selected: " + filename) # Print full path
                    # inkex.errormsg( "Selected file: " + str(os.path.basename(filename))) # Print file name
                elif response == gtk.RESPONSE_CANCEL:
                    inkex.errormsg(gettext.gettext('No CSV file selected.'))
                filter.destroy()
                dialog.destroy()

                if filename is not None:

                    CSVfile = open(filename, 'rU')
                    try:
                        dialect_read = csv.Sniffer().sniff(CSVfile.readline())
                    except:
                        dialect_read = None
                        inkex.errormsg( "Unable to determine format of selected file, " \
                                + str(os.path.basename(filename)) ) # Print file name

                    if dialect_read is None:
                        CSVfile.close()
                    else:
                        CSVfile.seek(0)  # Rewind file to beginning

                        reader = csv.reader(CSVfile, dialect=dialect_read)
                        CSVrowCount = sum(
                            1
                            for row in reader) - 1  # Subtract 1 for header row
                        CSVfile.seek(0)

                        if (CSVrowCount > 0):
                            CSVfile = open(filename, 'rU')
                            reader = csv.DictReader(CSVfile,
                                                    dialect=dialect_read)

                            inkex.errormsg( "Found " + str(CSVrowCount) + " Rows of merge data in file " \
                                        + str(os.path.basename(filename))) # Print file name
                            key_names = "Column names: "
                            for item in reader.fieldnames:
                                key_names = key_names + "{{" + item + "}}, "
                            key_names = key_names[:
                                                  -2]  # drop last two characters from string (", ")
                            inkex.errormsg(key_names)  # Print key list

                            self.csv_file_path = str(
                                filename)  # Path & Name of the file
                            self.storeCSVpath(
                                self.svg
                            )  # Store path & name file in our SVG file.
                        else:
                            inkex.errormsg(
                                "Unable to interpret selected file" +
                                str(os.path.basename(filename)) + ".")
                        CSVfile.close()

            elif self.options.data_action == "view":
                self.csv_data_read = False
                CSVfile = None
                csvNode = None
                for node in self.svg:
                    if node.tag == 'svg':
                        for subNode in self.svg:
                            if subNode.tag == inkex.addNS(
                                    'MergeData',
                                    'svg') or subNode.tag == 'MergeData':
                                csvNode = subNode
                    elif node.tag == inkex.addNS(
                            'MergeData', 'svg') or node.tag == 'MergeData':
                        csvNode = node
                if csvNode is not None:
                    try:
                        CSVfile = str(csvNode.text)
                        self.csv_data_read = True
                    except:
                        self.svg.remove(
                            csvNode
                        )  # An error before this point leaves csvDataRead as False.

                if CSVfile is None:
                    inkex.errormsg(
                        "No CSV data found in file. Please select and load a CSV file."
                    )
                    return
                else:
                    inkex.errormsg("The selected CSV data file is:")
                    inkex.errormsg(CSVfile)

#             elif self.options.data_action == "erase":
#                 self.eraseCSVpath(self.svg)    # erase stored CSV file from our SVG file.

        if self.serial_port is not None:
            ebb_motion.doTimedPause(
                self.serial_port,
                10)  #Pause a moment for underway commands to finish...
            ebb_serial.closePort(self.serial_port)
Esempio n. 7
0
    def _init_axidraw(self):
        self._port = ebb_serial.openPort()
        ebb_motion.sendDisableMotors(self._port)

        # Ratio experiment:
        # @152 dpi 1 pixel right and down
        # ebb_motion.doABMove(port, 20, -20, 200)
        # ebb_motion.doABMove(port, 15, -15, 200)

        # Millimeter factor (should be correlated as closely as possible to the
        # move value needed to move one millimeter). Multiply with millimeter
        # value.
        # 15 (move points to pattern pixel ratio; estimation from ratio
        # experiment)
        # (1/152) * 25.4 = 0.16710526315789473 (mm per pattern pixel)
        # 1/0.16710526315789473 = 5.984.. (=> ~6 pattern pixel per mm)
        # 15 * 6 = 90 (~90 move points per mm)
        # mm = 90
        # Further testing showed it should be less than 90
        # mm = 80
        # Manual testing with a 127dpi paper (1 pattern pixel is exactly 0.2mm)
        # showed that x- and y-axis differ a bit and that the values are
        # slightly higher than 80.
        # Also the first move to the other side on the y-axis is a bit skewed
        # (~0.2mm). There could maybe be code to compensate for the direction
        # change inaccuracy.
        # x_mm = 82
        # y_mm = 85
        # Manual testing over full DIN A4 papers yielded these results:
        self._x_mm = 80.2
        self._y_mm = 80.2
        # https://github.com/ertdfgcvb/Genau
        # This libraries README also states almost the same values:
        # "where 80 steps = 1mm"

        # From the "EBB (EiBotBoard) Command Set" documentation:
        # "Minimum speed is 1.31 steps/second."
        # "Maximum speed is 25k Steps/second."
        self._max_vel = 25000
        self._min_vel = 1.31
        # Calculate maximum and minimum millisecond divider
        self._max_ms = self._max_vel / 1000
        self._min_ms = self._min_vel / 1000
        # Manual/Sensible maximum and minimum values for millisecond divider
        # These are not the absolute maximum and minimum values but should be
        # more than enough for our purposes.
        # self._max_ms = 20
        # self._min_ms = 0.1
        # max_duration does not really matter in our context
        # min_duration = 4 @ 80 move points => 20 ms/step
        # BUT:
        # our min_steps are 26 => duration = math.ceil(1.3) = 2 @ 20 ms/step
        # TODO: Maybe use the same maximum/minimum as in the AxiDraw Inkscape
        # plugin
        # Maximum:
        # Would decrement self._ms by 1 until it is smaller than self._max_ms.
        # https://github.com/evil-mad/axidraw/blob/master/inkscape%20driver/axidraw_conf.py#L112
        # Minimum (is hardcoded):
        # If self._ms value is smaller than 0.002 it would jump to 0.
        # https://github.com/evil-mad/axidraw/blob/master/inkscape%20driver/axidraw.py#L2373
        # self._max_ms = 24.995
        # self._min_ms = 0.002

        # According to the User Guide lower percentages are better for accuracy
        # (p. 35).
        # 0% will be self._min_ms and 100% self._max_ms. That means that
        # even if self._vel_percent is 0 that the AxiDraw will still move at
        # the minimum velocity.
        # The 15% value should give pretty good accuracy and work pretty well
        # for small and big distances.
        self._vel_percent = 15
        if self._vel_percent < 0 or self._vel_percent > 100:
            raise ValueError("The self._vel_percent variable " +
                             f"({self._vel_percent}) is not between " +
                             "0 and 100 (both ends included).")

        # Time divider
        # Divide by the max_steps value to get the move duration in
        # milliseconds.
        min_max_delta = self._max_ms - self._min_ms
        self._ms = (min_max_delta * (self._vel_percent / 100)) + self._min_ms
        # TODO/FIXME: This calculation with vel_percent value does not seem to
        # do the same as the AxiDraws with the drawing speed percentage.
        # In the AxiDraw Inkscape plugin code the ms value at high resolution
        # and 15% drawing speed seems to be around ~2.41
        # Example:
        # # TODO: Not confirmed yet from AxiDraw code. Should be extremely
        # # close though because it works very well in tests.
        # steps_per_mm = 80
        # # drawing speed pecentage (needs to be between 1 and 110 [for some
        # # reason]) Default: 25
        # pen_down_speed = 15
        # # Maximum XY speed allowed when in High Resolution mode, in inches
        # # per second. Default: 8.6979, Max: 8.6979
        # SpeedLimXY_HR = 8.6979
        # # Speed given as maximum inches/second in XY plane
        # inch_per_s = pen_down_speed * SpeedLimXY_HR / 110.0
        # ms = (inch_per_s / 1000) * 25.4 * steps_per_mm  # == ~2.41010
        self._ms = 2.41

        # Direction factor. Multiply with move value to get right sign for
        # direction.
        # Assumes the AxiDraw sits to the right of the pattern paper.
        self._up, self._right = -1, -1
        self._down, self._left = 1, 1

        # Use 5 columns and take 20 frames per column to get 100 frames total
        # per page.
        num_cols = 5
        frames_per_col = 20

        # Calculate column move deltas
        x_points = np.linspace(0, self._move_size[0], num_cols)
        # Calculate millimeter deltas for columns.
        self._x_deltas = np.diff(x_points)
        # self._x_delta_sum = np.sum(self._x_deltas)
        # print(f"self._x_deltas: {self._x_deltas}")
        # print(f"self._x_delta_sum: {self._x_delta_sum}")
        # Calculate move value deltas for columns.
        # Because of rounding errors there is a difference between the full
        # page move (move_size * mm) and the move_delta_sum. Since this error
        # is very small it is ignored.
        # TODO: Maybe handle these errors better.
        self._x_move_deltas = (self._x_deltas * self._x_mm).astype(int)
        # self._x_move_delta_sum = np.sum(self._x_move_deltas)
        # print(f"self._x_move_deltas: {self._x_move_deltas}")
        # print(f"self._x_move_delta_sum: {self._x_move_delta_sum}")

        # Decrease the distance between frames within a column to also get an
        # idea of the accuracy of the readings.
        # Space out values logarithmically
        y_points = np.geomspace(self._move_size[1] + 1, 1, frames_per_col)
        # Calculate millimeter deltas.
        self._y_deltas = np.abs(np.diff(y_points))
        # self._y_delta_sum = np.sum(self._y_deltas)
        # print(f"self._y_deltas: {self._y_deltas}")
        # print(f"self._y_delta_sum: {self._y_delta_sum}")
        # Calculate move value deltas.
        # Because of rounding errors there is a difference between the full
        # page move (move_size * mm) and the move_delta_sum. Since this error
        # is very small it is ignored.
        # TODO: Maybe handle these errors better.
        self._y_move_deltas = (self._y_deltas * self._y_mm).astype(int)