def __init__(self, parent=None): BaseFrame.__init__(self, parent, -1, lang.getstr("measurement_report")) self.Bind(wx.EVT_CLOSE, self.OnClose) self.SetIcons(config.get_icon_bundle([256, 48, 32, 16], appname)) res = TempXmlResource(get_data_path(os.path.join("xrc", "report.xrc"))) res.InsertHandler(xh_fancytext.StaticFancyTextCtrlXmlHandler()) res.InsertHandler( xh_filebrowsebutton.FileBrowseButtonWithHistoryXmlHandler()) res.InsertHandler(xh_hstretchstatbmp.HStretchStaticBitmapXmlHandler()) res.InsertHandler(xh_bitmapctrls.BitmapButton()) res.InsertHandler(xh_bitmapctrls.StaticBitmap()) self.panel = res.LoadPanel(self, "panel") self.Sizer = wx.BoxSizer(wx.VERTICAL) self.Sizer.Add(self.panel, 1, flag=wx.EXPAND) self.set_child_ctrls_as_attrs(self) self.measurement_report_btn = wx.Button(self.panel, -1, lang.getstr("measure")) self.panel.Sizer.Insert(2, self.measurement_report_btn, flag=wx.RIGHT | wx.BOTTOM | wx.ALIGN_RIGHT, border=16) self.worker = worker.Worker(self) self.worker.set_argyll_version("xicclu") BaseFrame.setup_language(self) self.mr_setup_language() self.mr_init_controls() self.mr_update_controls() self.mr_init_frame()
def _validate(self, resp, url, validate): raw = resp.read() if isinstance(validate, dict): data = json.loads(raw) components = urllib.parse.urlparse(url) api = components.path[1:] query = urllib.parse.parse_qs(components.query) if "m" in query: method = query["m"][0] if data.get(method) == "Error" and "msg" in data: raise http.client.HTTPException("%s: %s" % (self.host, data["msg"])) for key, value in validate.items(): if key not in data: raise http.client.HTTPException( lang.getstr("response.invalid.missing_key", (self.host, key, raw))) elif value is not None and data[key] != value: raise http.client.HTTPException( lang.getstr("response.invalid.value", (self.host, key, value, raw))) data["raw"] = raw return data elif validate: if raw != validate: raise http.client.HTTPException( lang.getstr("response.invalid", (self.host, raw))) return raw
def wait(self): self.listening = True if self.logfile: try: host = get_network_addr() except error: host = gethostname() self.logfile.write( lang.getstr("webserver.waiting") + (" %s:%s\n" % (host, self.port))) self.socket.settimeout(1) while self.listening: try: self.conn, addr = self.get_request() except timeout: continue self.conn.settimeout(1) break self.socket.settimeout(None) if self.listening: try: self.process_request(self.conn, addr) except: self.handle_error(self.conn, addr) self.disconnect_client() else: self._thread = threading.Thread( target=self.serve_forever, name="WebWinHTTPPatternGeneratorServerThread") self._thread.start() safe_print(lang.getstr("connection.established"))
def wait(self): self.listening = True if self.logfile: self.logfile.write( lang.getstr("connecting.to", ("Chromecast", " " + self.name)) + "\n") if not hasattr(self, "_cc"): # Find our ChromeCast try: self._cc = next(cc for cc in get_chromecasts() if safe_unicode( cc.device.friendly_name, "UTF-8") == self.name) except StopIteration: self.listening = False raise self._cc.register_handler(self._controller) # Wait for ChromeCast device to be ready while self.listening: self._cc.wait(0.05) if self._cc.status: break if self.listening: # Launch pattern generator app self._controller.launch() # Wait for it while (self.listening and self._cc.app_id != self._controller.supporting_app_id): sleep(0.05) self.conn = True safe_print(lang.getstr("connection.established"))
def toggle_draw(self, event): """ Toggle between spectral and CIE plot """ if self.canvas.GetEnableLegend(): self.draw_ccxx() self.toggle_btn.SetLabel(lang.getstr("spectral")) else: self.draw_cie() self.toggle_btn.SetLabel(lang.getstr("whitepoint.xy"))
def __init__(self, html, embed, view, force, cache): BaseFrame.__init__(self, None, wx.ID_ANY, lang.getstr("vrml_to_x3d_converter"), style=wx.DEFAULT_FRAME_STYLE & ~(wx.MAXIMIZE_BOX | wx.RESIZE_BORDER), name="vrml2x3dframe") self.SetIcons( config.get_icon_bundle([256, 48, 32, 16], appname + "-VRML-to-X3D-converter")) self.Bind(wx.EVT_CLOSE, self.OnClose) self.cache = cache self.embed = embed self.force = force self.html = html self.worker = Worker(self) sizer = wx.BoxSizer(wx.HORIZONTAL) self.SetSizer(sizer) panel = wx.Panel(self) sizer.Add(panel) panelsizer = wx.BoxSizer(wx.HORIZONTAL) panel.SetSizer(panelsizer) self.btn = BitmapButton(panel, wx.ID_ANY, config.geticon(256, "3d-primitives"), style=wx.NO_BORDER) self.btn.SetToolTipString(lang.getstr("file.select")) self.btn.Bind( wx.EVT_BUTTON, lambda event: vrmlfile2x3dfile(None, html=html, embed=embed, view=view, force=force, cache=cache, worker=self.worker)) self.droptarget = FileDrop(self) vrml_drop_handler = lambda vrmlpath: vrmlfile2x3dfile(vrmlpath, html=html, embed=embed, view=view, force=force, cache=cache, worker=self. worker) self.droptarget.drophandlers = { ".vrml": vrml_drop_handler, ".vrml.gz": vrml_drop_handler, ".wrl": vrml_drop_handler, ".wrl.gz": vrml_drop_handler, ".wrz": vrml_drop_handler } self.btn.SetDropTarget(self.droptarget) panelsizer.Add(self.btn, flag=wx.ALL, border=12) self.Fit() self.SetMinSize(self.GetSize()) self.SetMaxSize(self.GetSize())
def create(report_path, placeholders2data, pack=True, templatename="report"): """ Create a report with all placeholders substituted by data. """ # read report template templatefilename = "%s.html" % templatename report_html_template_path = get_data_path( os.path.join("report", templatefilename)) if not report_html_template_path: raise IOError(lang.getstr("file.missing", templatefilename)) try: report_html_template = codecs.open(report_html_template_path, "r", "UTF-8") except (IOError, OSError) as exception: raise exception.__class__( lang.getstr("error.file.open", report_html_template_path)) report_html = report_html_template.read() report_html_template.close() # create report for placeholder, data in placeholders2data.items(): report_html = report_html.replace(placeholder, data) for include in ("base.css", "compare.css", "print.css", "jsapi-packages.js", "jsapi-patches.js", "compare.constants.js", "compare.variables.js", "compare.functions.js", "compare.init.js", "uniformity.functions.js"): path = get_data_path(os.path.join("report", include)) if not path: raise IOError(lang.getstr("file.missing", include)) try: f = codecs.open(path, "r", "UTF-8") except (IOError, OSError) as exception: raise exception.__class__(lang.getstr("error.file.open", path)) if include.endswith(".js"): js = f.read() if pack: packer = jspacker.JavaScriptPacker() js = packer.pack(js, 62, True).strip() report_html = report_html.replace( 'src="%s">' % include, ">/*<![CDATA[*/\n" + js + "\n/*]]>*/") else: report_html = report_html.replace('@import "%s";' % include, f.read().strip()) f.close() # write report try: report_html_file = codecs.open(report_path, "w", "UTF-8") except (IOError, OSError) as exception: raise exception.__class__( lang.getstr("error.file.create", report_path) + "\n\n" + safe_unicode(exception)) report_html_file.write(report_html) report_html_file.close()
def vrmlfile2x3dfile(vrmlpath, x3dpath, html=True, embed=False, force=False, cache=True, worker=None): """ Convert VRML file located at vrmlpath to HTML and write to x3dpath """ filename, ext = os.path.splitext(vrmlpath) if ext.lower() in (".gz", ".wrz"): cls = GzipFileProper else: cls = open with cls(vrmlpath, "rb") as vrmlfile: vrml = vrmlfile.read() if worker: worker.recent.write( "%s %s\n" % (lang.getstr("converting"), os.path.basename(vrmlpath))) _safe_print(lang.getstr("converting"), vrmlpath) filename, ext = os.path.splitext(x3dpath) try: x3d = vrml2x3dom(vrml, worker) if not x3d: _safe_print(lang.getstr("aborted")) return False if not html: _safe_print("Writing", x3dpath) with open(x3dpath, "wb") as x3dfile: x3dfile.write(x3d.x3d()) else: html = x3d.html(title=os.path.basename(filename), embed=embed, force=force, cache=cache) _safe_print("Writing", x3dpath + ".html") with open(x3dpath + ".html", "wb") as htmlfile: htmlfile.write(html) except KeyboardInterrupt: x3d = False except VRMLParseError as exception: return exception except EnvironmentError as exception: return exception except Exception as exception: import traceback _safe_print(traceback.format_exc()) return exception return True
def send_command_handler(self, command, additional_commands=None): self.add_text("\n") command = command.strip() if not command: self.add_text("> ") return data = split_command_line(command) response = self.process_data_local(data) if response == "ok": pass elif isinstance(response, list): self.add_text("\n".join(response)) self.add_text("\n> ") elif not self or not self.conn: self.add_error_text(lang.getstr("not_connected") + "\n") self.add_text("> ") else: self.busy = True delayedresult.startWorker(self.check_result, self.send_command, cargs=(self.get_response, additional_commands), wargs=(command, )) self.history.insert(len(self.history) - 1, command) if len(self.history) > 1000: del self.history[0] self.historypos = len(self.history) - 1
def get_app_info(self): commands = ["setresponseformat plain", "getcommands", "getappname"] try: for command in commands: self.conn.send_command(command) response = self.conn.get_single_response() if command == "getcommands": self.commands = response.splitlines() elif command == "getappname": wx.CallAfter( self.add_text, lang.getstr("connected.to.at", ((response, ) + self.conn.getpeername())) + "\n%s\n" % lang.getstr("scripting-client.cmdhelptext")) except socket.error as exception: return exception
def disconnect(self): if self.conn: try: peer = self.conn.getpeername() self.conn.shutdown(socket.SHUT_RDWR) except socket.error as exception: if exception.errno != errno.ENOTCONN: self.add_text(safe_unicode(exception) + "\n") else: self.add_text(lang.getstr("disconnected.from", peer) + "\n") self.conn.close() del self.conn self.conn = None self.commands = [] else: self.add_error_text(lang.getstr("not_connected") + "\n")
def connect_handler(self, ip_port): ip, port = ip_port.split(":", 1) try: port = int(port) except ValueError: self.add_error_text(lang.getstr("port.invalid", port) + "\n") self.add_text("> ") return if self.conn: self.disconnect() self.add_text(lang.getstr("connecting.to", (ip, port)) + "\n") self.busy = True delayedresult.startWorker(self.check_result, self.connect, cargs=(self.get_app_info, None, False), wargs=(ip, port))
def mr_setup_language(self): # Shared with main window for which in ("chart", "simulation_profile", "devlink_profile", "output_profile"): if which.endswith("_profile"): wildcard = lang.getstr("filetype.icc") + "|*.icc;*.icm" else: wildcard = (lang.getstr("filetype.ti1_ti3_txt") + "|*.cgats;*.cie;*.ti1;*.ti2;*.ti3;*.txt") msg = { "chart": "measurement_report_choose_chart_or_reference", "devlink_profile": "devicelink_profile", "output_profile": "measurement_report_choose_profile" }.get(which, which) kwargs = dict(toolTip=lang.getstr(msg).rstrip(":"), dialogTitle=lang.getstr(msg), fileMask=wildcard) ctrl = getattr(self, "%s_ctrl" % which) for name, value in kwargs.items(): setattr(ctrl, name, value) items = [] for item in ("Gamma 2.2", "trc.rec1886", "custom"): items.append(lang.getstr(item)) self.mr_trc_ctrl.SetItems(items) self.trc_gamma_types_ab = {0: "b", 1: "B"} self.trc_gamma_types_ba = {"b": 0, "B": 1} self.mr_trc_gamma_type_ctrl.SetItems([ lang.getstr("trc.type.relative"), lang.getstr("trc.type.absolute") ])
def __call__(self, idata): if not isinstance(idata, str): verbose = self.verbose if self.convert_video_rgb_to_clut65: devi_devip = self.devi_devip else: devi_devip = lambda v: v scale = float(self.scale) idata = list(idata) # Make a copy for i, v in enumerate(idata): if isinstance(v, (float, int)): self([idata]) return if not isinstance(v, str): if verbose: for n in v: if not isinstance(n, (float, int)): raise TypeError("xicclu: Expecting list of " "strings or n-tuples with " "floats") idata[i] = " ".join( str(devi_devip(n / scale) * scale) for n in v) else: idata = idata.splitlines() numrows = len(idata) chunklen = 1000 i = 0 p = self.subprocess prevperc = -1 while True: # Process in chunks to prevent broken pipe if input data is too # large if getattr(sys, "_sigbreak", False) and not self.subprocess_abort: self.subprocess_abort = True safe_print("Got SIGBREAK, aborting subprocess...") if self.subprocess_abort or self.thread_abort: if p.poll() is None: p.stdin.write("\n") p.stdin.close() p.wait() raise Info(lang.getstr("aborted")) if p.poll() is None: # We don't use communicate() because it will end the # process p.stdin.write("\n".join(idata[chunklen * i:chunklen * (i + 1)]) + "\n") p.stdin.flush() else: # Error break perc = round(chunklen * (i + 1) / float(numrows) * 100) if perc > prevperc and self.logfile: self.logfile.write("\r%i%%" % min(perc, 100)) prevperc = perc if chunklen * (i + 1) > numrows - 1: break i += 1
def wait(self): self.listening = True if self.logfile: try: host = get_network_addr() except error: host = gethostname() self.logfile.write( lang.getstr("connection.waiting") + (" %s:%s\n" % (host, self.port))) while self.listening: try: self.conn, addr = self.socket.accept() except timeout: continue self.conn.settimeout(1) break if self.listening: safe_print(lang.getstr("connection.established"))
def measure_darken_background_ctrl_handler(self, event): if self.measure_darken_background_cb.GetValue() and \ getcfg("measure.darken_background.show_warning"): dlg = ConfirmDialog(self, msg=lang.getstr("measure.darken_background." "warning"), ok=lang.getstr("ok"), cancel=lang.getstr("cancel"), bitmap=geticon(32, "dialog-warning")) chk = wx.CheckBox(dlg, -1, lang.getstr("dialog.do_not_show_again")) dlg.Bind(wx.EVT_CHECKBOX, self.measure_darken_background_warning_handler, id=chk.GetId()) dlg.sizer3.Add(chk, flag=wx.TOP | wx.ALIGN_LEFT, border=12) dlg.sizer0.SetSizeHints(dlg) dlg.sizer0.Layout() rslt = dlg.ShowModal() if rslt == wx.ID_CANCEL: self.measure_darken_background_cb.SetValue(False) setcfg("measure.darken_background", int(self.measure_darken_background_cb.GetValue()))
def get_single_response(self): # Buffer received data until EOT (response end marker) and return # single response (additional data will still be in the buffer) while not "\4" in self.recv_buffer: incoming = self.recv(4096) if incoming == "": raise socket.error(lang.getstr("connection.broken")) self.recv_buffer += incoming end = self.recv_buffer.find("\4") single_response = self.recv_buffer[:end] self.recv_buffer = self.recv_buffer[end + 1:] return safe_unicode(single_response, "UTF-8")
def __init__(self, profile, intent="r", direction="f", order="n", pcs=None, scale=1, cwd=None, startupinfo=None, use_icclu=False, use_cam_clipping=False, logfile=None, worker=None, show_actual_if_clipped=False, input_encoding=None, output_encoding=None, output_format=None, reverse=False, output_stream=None, convert_video_rgb_to_clut65=False, verbose=1): WorkerBase.__init__(self) self.logfile = logfile self.worker = worker self.output_stream = output_stream self._in = [] self._args = (profile.fileName, intent, direction, order, pcs, scale, cwd, startupinfo, use_icclu, use_cam_clipping, None, show_actual_if_clipped, input_encoding, output_encoding, lang.getstr("aborted"), output_format, reverse, convert_video_rgb_to_clut65, verbose) self._out = [] num_cpus = mp.cpu_count() if isinstance(profile.tags.get("A2B0"), ICCP.LUT16Type): size = profile.tags.A2B0.clut_grid_steps self.num_workers = min(max(num_cpus, 1), size) if num_cpus > 2: self.num_workers = int(self.num_workers * 0.75) self.num_batches = size // 9 else: if num_cpus > 2: self.num_workers = 2 else: self.num_workers = num_cpus self.num_batches = 1
def check_result(self, delayedResult, get_response=False, additional_commands=None, colorize=True): try: result = delayedResult.get() except Exception as exception: if hasattr(exception, "originalTraceback"): self.add_text(exception.originalTraceback) result = exception if result: if isinstance(result, socket.socket): self.conn = result result = lang.getstr("connection.established") text = "%s\n" % safe_unicode(result) self.add_text(text) if colorize or isinstance(result, Exception): end = self.console.GetLastPosition() start = end - len(text) if isinstance(result, Exception): color = ERRORCOLOR else: color = RESPONSECOLOR self.mark_text(start, end, color) if get_response and not isinstance(result, Exception): delayedresult.startWorker(self.check_result, get_response, cargs=(False, additional_commands)) else: self.add_text("> ") if additional_commands: self.add_text(additional_commands[0].rstrip("\n")) if additional_commands[0].endswith("\n"): self.send_command_handler(additional_commands[0], additional_commands[1:]) return self.console.SetFocus() self.busy = False
def parse_txt(self, txt): if not txt: return self.logger.info("%r" % txt) if "Setting up the instrument" in txt: self.Pulse(lang.getstr("instrument.initializing")) if "Spot read failed" in txt: self.last_error = txt if "Result is XYZ:" in txt: # Result is XYZ: d.dddddd d.dddddd d.dddddd, D50 Lab: d.dddddd d.dddddd d.dddddd # CCT = ddddK (Delta E d.dddddd) # Closest Planckian temperature = ddddK (Delta E d.dddddd) # Closest Daylight temperature = ddddK (Delta E d.dddddd) XYZ = re.search("XYZ:\s+(\d+\.\d+)\s+(\d+\.\d+)\s+(\d+\.\d+)", txt) self.results[self.index].append( {"XYZ": [float(value) for value in XYZ.groups()]}) self.last_error = None loci = {"t": "Daylight", "T": "Planckian"} for locus in list(loci.values()): if locus in txt: CT = re.search( "Closest\s+%s\s+temperature\s+=\s+(\d+)K" % locus, txt, re.I) self.results[self.index][-1]["C%sT" % locus[0]] = int( CT.groups()[0]) if "key to take a reading" in txt and not self.last_error: safe_print("%s: Got 'key to take a reading'" % appname) if not self.is_measuring: self.enable_buttons() return if len(self.results[self.index]) < len(self.colors): # Take readings at 5 different brightness levels per swatch safe_print("%s: About to take next reading" % appname) self.measure() else: self.is_measuring = False self.show_cursor() self.enable_buttons() self.buttons[self.index].Show() self.buttons[self.index].SetFocus() self.buttons[self.index].SetBitmap( getbitmap("theme/icons/16x16/checkmark")) self.panels[self.index].SetBackgroundColour(BGCOLOUR) self.panels[self.index].Refresh() self.panels[self.index].Update() if len(self.results) == self.rows * self.cols: # All swatches have been measured, show results # Let the user choose a location for the results html display_no, geometry, client_area = self.get_display() # Translate from wx display index to Argyll display index geometry = "%i, %i, %ix%i" % tuple(geometry) for i, display in enumerate(getcfg("displays")): if display.find("@ " + geometry) > -1: safe_print("Found display %s at index %i" % (display, i)) break display = display.replace(" [PRIMARY]", "") defaultFile = "Uniformity Check %s — %s — %s" % ( appversion, re.sub(r"[\\/:*?\"<>|]+", "_", display), strftime("%Y-%m-%d %H-%M.html")) defaultDir = get_verified_path( None, os.path.join(getcfg("profile.save_path"), defaultFile))[0] dlg = wx.FileDialog(self, lang.getstr("save_as"), defaultDir, defaultFile, wildcard=lang.getstr("filetype.html") + "|*.html;*.htm", style=wx.SAVE | wx.FD_OVERWRITE_PROMPT) dlg.Center(wx.BOTH) result = dlg.ShowModal() if result == wx.ID_OK: path = dlg.GetPath() if not waccess(path, os.W_OK): from worker import show_result_dialog show_result_dialog( Error( lang.getstr("error.access_denied.write", path)), self) return save_path = os.path.splitext(path)[0] + ".html" setcfg("last_filedialog_path", save_path) dlg.Destroy() if result != wx.ID_OK: return locus = loci.get(getcfg("whitepoint.colortemp.locus")) try: report.create( save_path, { "${REPORT_VERSION}": appversion, "${DISPLAY}": display, "${DATETIME}": strftime("%Y-%m-%d %H:%M:%S"), "${ROWS}": str(self.rows), "${COLS}": str(self.cols), "${RESULTS}": str(self.results), "${LOCUS}": locus }, getcfg("report.pack_js"), "uniformity") except (IOError, OSError) as exception: from worker import show_result_dialog show_result_dialog(exception, self) else: launch_file(save_path) if getcfg("uniformity.measure.continuous"): self.measure(event=Event(self.buttons[self.index]))
def __init__(self, parent, cgats, worker=None): """ Init new CCXPlot window. parent Parent window (only used for error dialogs) cgats A CCMX/CCSS CGATS instance worker Worker instance """ self.is_ccss = cgats[0].type == "CCSS" desc = cgats.get_descriptor() if cgats.filename: fn, ext = os.path.splitext(os.path.basename(cgats.filename)) else: fn = desc if self.is_ccss: ext = ".ccss" else: ext = ".ccmx" desc = lang.getstr(ext[1:] + "." + fn, default=desc) if self.is_ccss: ccxx_type = "spectral" else: ccxx_type = "matrix" title = "%s: %s" % (lang.getstr(ccxx_type), desc) if self.is_ccss: # Convert to TI3 so we can get XYZ from spectra for coloring temp = worker.create_tempdir() if isinstance(temp, Exception): show_result_dialog(temp, parent) else: basename = make_filename_safe(desc) temp_path = os.path.join(temp, basename + ".ti3") cgats[0].type = "CTI3" cgats[0].DEVICE_CLASS = "DISPLAY" cgats.write(temp_path) temp_out_path = os.path.join(temp, basename + ".CIE.ti3") result = worker.exec_cmd(get_argyll_util("spec2cie"), [temp_path, temp_out_path], capture_output=True) if isinstance(result, Exception) or not result: show_result_dialog( UnloggedError(result or "".join(worker.errors)), parent) worker.wrapup(False) else: try: cgats = CGATS.CGATS(temp_out_path) except Exception as exception: show_result_dialog(exception, parent) finally: worker.wrapup(False) data_format = cgats.queryv1("DATA_FORMAT") data = cgats.queryv1("DATA") XYZ_max = 0 self.samples = [] if self.is_ccss: x_min = cgats.queryv1("SPECTRAL_START_NM") x_max = cgats.queryv1("SPECTRAL_END_NM") bands = cgats.queryv1("SPECTRAL_BANDS") lores = bands <= 40 if lores: # Interpolate if lores # 1nm intervals steps = int(x_max - x_min) + 1 safe_print("Up-interpolating", bands, "spectral bands to", steps) step = (x_max - x_min) / (steps - 1.) else: step = (x_max - x_min) / (bands - 1.) y_min = 0 y_max = 1 Y_max = 0 for i, sample in data.items(): # Get nm and spectral power values = [] x = x_min for k in data_format.values(): if k.startswith("SPEC_"): y = sample[k] y_min = min(y, y_min) y_max = max(y, y_max) if lores: values.append(y) else: values.append((x, y)) x += step if lores: # Interpolate if lores. Use Catmull-Rom instead of # PolySpline as we want curves to go through points exactly numvalues = len(values) interp = ICCP.CRInterpolation(values) values = [] for i in range(steps): values.append( (x, interp(i / (steps - 1.) * (numvalues - 1.)))) x += step # Get XYZ for colorization XYZ = [] for component in "XYZ": label = "XYZ_" + component if label in sample: v = sample[label] XYZ_max = max(XYZ_max, v) if label == "XYZ_Y": Y_max = max(Y_max, v) XYZ.append(v) self.samples.append((XYZ, values, {})) Plot = plot.PolyLine Plot._attributes["width"] = 1 else: # CCMX cube_size = 2 x_min = 0 y_min = 0 mtx = colormath.Matrix3x3( [[sample[k] for k in data_format.values()] for sample in data.values()]) imtx = mtx.inverted() # Get XYZ that colorimeter would measure without matrix (sRGB ref, # so not accurate, but useful for visual representation which is all # we care about here) if cube_size == 2: scale = 1 x_max = 100 * scale y_max = x_max * (74.6 / 67.4) if sys.platform != "win32": x_center = x_max / 2. else: x_center = x_max / 2. - 2.5 y_center = y_max / 2. x_center *= scale y_center *= scale pos2rgb = [((x_center - 23.7, y_center - 13.7), (0, 0, 1)), ((x_center, y_center + 27.3), (0, 1, 0)), ((x_center + 23.7, y_center - 13.7), (1, 0, 0)), ((x_center - 23.7, y_center + 13.7), (0, 1, 1)), ((x_center, y_center - 27.3), (1, 0, 1)), ((x_center + 23.7, y_center + 13.7), (1, 1, 0)), ((x_center, y_center), (1, 1, 1))] attrs_c = {'size': 10} attrs_r = {'size': 5} else: x_max = 100 y_max = 100 y = -5 pos2rgb = [] for R in range(cube_size): for G in range(cube_size): x = -5 y += 10 for B in range(cube_size): x += 10 pos2rgb.append(((x, y), (v / (cube_size - 1.0) for v in (R, G, B)))) attrs_c = {'marker': 'square', 'size': 10} attrs_r = {'marker': 'square', 'size': 5} Y_max = (imtx * colormath.get_whitepoint("D65"))[1] for i, ((x, y), (R, G, B)) in enumerate(pos2rgb): XYZ = list(colormath.RGB2XYZ(R, G, B)) X, Y, Z = imtx * XYZ XYZ_max = max(XYZ_max, X, Y, Z) self.samples.append(([X, Y, Z], [(x, y)], attrs_c)) self.samples.append((XYZ, [(x, y)], attrs_r)) Plot = plot.PolyMarker if self.is_ccss: # Protect against division by zero when range is zero if not x_max - x_min: x_min = 350.0 x_max = 750.0 if not y_max - y_min: y_min = 0.0 y_max = 10.0 y_zero = 0 self.ccxx_axis_x = (math.floor(x_min / 50.) * 50, math.ceil(x_max / 50.) * 50) self.spec_x = (self.ccxx_axis_x[1] - self.ccxx_axis_x[0]) / 50. graph_range = nicenum(y_max - y_zero, False) d = nicenum(graph_range / (NTICK - 1.0), True) self.spec_y = math.ceil(y_max / d) self.ccxx_axis_y = (math.floor(y_zero / d) * d, self.spec_y * d) else: self.ccxx_axis_x = (math.floor(x_min / 20.) * 20, math.ceil(x_max / 20.) * 20) self.ccxx_axis_y = (math.floor(y_min), math.ceil(y_max)) self.gfx = [] for XYZ, values, attrs in self.samples: if len(XYZ) == 3: # Got XYZ if attrs.get("size") > 11.25: # Colorimeter XYZ if Y_max > 1: # Colorimeter brighter than ref XYZ[:] = [v / Y_max for v in XYZ] else: # Colorimeter dimmer than ref XYZ[:] = [v * Y_max for v in XYZ] else: # Ref XYZ if Y_max > 1: # Colorimeter brighter than ref XYZ[:] = [v / Y_max for v in XYZ] RGB = tuple( int(v) for v in colormath.XYZ2RGB(*XYZ, scale=255, round_=True)) else: RGB = (153, 153, 153) self.gfx.append(Plot(values, colour=wx.Colour(*RGB), **attrs)) if self.is_ccss: # Add a few points at the extremes to define a bounding box self.gfx.append( plot.PolyLine( [(self.ccxx_axis_x[0], self.ccxx_axis_y[0]), (self.ccxx_axis_x[1], self.ccxx_axis_y[1] - y_min)], colour=wx.Colour(0, 0, 0, 0))) ref = cgats.queryv1("REFERENCE") if ref: ref = get_canonical_instrument_name(safe_unicode(ref, "UTF-8")) if not self.is_ccss: observers_ab = {} for observer in config.valid_values["observer"]: observers_ab[observer] = lang.getstr("observer." + observer) x_label = [lang.getstr("matrix")] x_label.extend(["%9.6f %9.6f %9.6f" % tuple(row) for row in mtx]) if ref: ref_observer = cgats.queryv1("REFERENCE_OBSERVER") if ref_observer: ref += ", " + observers_ab.get(ref_observer, ref_observer) x_label.append("") x_label.append(ref) fit_method = cgats.queryv1("FIT_METHOD") if fit_method == "xy": fit_method = lang.getstr("ccmx.use_four_color_matrix_method") elif fit_method: fit_method = lang.getstr("perceptual") fit_de00_avg = cgats.queryv1("FIT_AVG_DE00") if not isinstance(fit_de00_avg, float): fit_de00_avg = None fit_de00_max = cgats.queryv1("FIT_MAX_DE00") if not isinstance(fit_de00_max, float): fit_de00_max = None if fit_method: x_label.append(fit_method) fit_de00 = [] if fit_de00_avg: fit_de00.append( "ΔE*00 %s %.4f" % (lang.getstr("profile.self_check.avg"), fit_de00_avg)) if fit_de00_max: fit_de00.append( "ΔE*00 %s %.4f" % (lang.getstr("profile.self_check.max"), fit_de00_max)) if fit_de00: x_label.append("\n".join(fit_de00)) x_label = "\n".join(x_label) else: x_label = "" if ref: x_label += ref + ", " x_label += "%.1fnm, %i-%inm" % ((x_max - x_min) / (bands - 1.0), x_min, x_max) scale = max(getcfg("app.dpi") / config.get_default_dpi(), 1) style = wx.DEFAULT_FRAME_STYLE wx.Frame.__init__(self, None, -1, title, style=style) self.SetIcons(config.get_icon_bundle([256, 48, 32, 16], appname)) self.SetBackgroundColour(BGCOLOUR) self.Sizer = wx.GridSizer(1, 1, 0, 0) bg = wx.Panel(self) bg.SetBackgroundColour(BGCOLOUR) bg.Sizer = wx.BoxSizer(wx.VERTICAL) self.canvas = canvas = LUTCanvas(bg) if self.is_ccss: bg.MinSize = (513 * scale, 557 * scale) btnsizer = wx.BoxSizer(wx.HORIZONTAL) bg.Sizer.Add(btnsizer, flag=wx.EXPAND | wx.TOP | wx.RIGHT | wx.LEFT, border=16) self.toggle_btn = FlatShadedButton(bg, -1, label=lang.getstr("spectral")) btnsizer.Add(self.toggle_btn, 1) self.Sizer.Add(bg, 1, flag=wx.EXPAND) bg.Sizer.Add(canvas, 1, flag=wx.EXPAND) else: self.Sizer.Add(bg, flag=wx.ALIGN_CENTER) canvas_w = 240 * scale canvas.MinSize = (canvas_w, canvas_w * (74.6 / 67.4)) bg.Sizer.Add(canvas, flag=wx.ALIGN_CENTER) label = wx.StaticText(bg, -1, x_label.replace("&", "&&"), style=wx.ALIGN_CENTRE_HORIZONTAL) label.SetForegroundColour(FGCOLOUR) label.SetMaxFontSize(11) bg.Sizer.Add(label, flag=wx.ALIGN_CENTER | wx.ALL & ~wx.TOP, border=16 * scale) canvas.SetBackgroundColour(BGCOLOUR) canvas.SetEnableCenterLines(False) canvas.SetEnableDiagonals(False) canvas.SetEnableGrid(True) canvas.enableTicks = (True, True) canvas.tickPen = wx.Pen(GRIDCOLOUR, canvas._pointSize[0]) canvas.SetEnablePointLabel(False) canvas.SetEnableTitle(True) canvas.SetForegroundColour(FGCOLOUR) canvas.SetGridColour(GRIDCOLOUR) canvas.canvas.BackgroundColour = BGCOLOUR if self.is_ccss: canvas.HandCursor = wx.StockCursor(wx.CURSOR_SIZING) canvas.SetCursor(canvas.HandCursor) else: canvas.canvas.Unbind(wx.EVT_LEFT_DCLICK) canvas.SetEnableDrag(False) canvas.SetCursor(wx.StockCursor(wx.CURSOR_DEFAULT)) canvas.SetXSpec('none') canvas.SetYSpec('none') # CallAfter is needed under GTK as usual wx.CallAfter(self.draw_ccxx) if self.is_ccss: self.Bind(wx.EVT_KEY_DOWN, self.key_handler) for child in self.GetAllChildren(): child.Bind(wx.EVT_KEY_DOWN, self.key_handler) child.Bind(wx.EVT_MOUSEWHEEL, self.OnWheel) self.toggle_btn.Bind(wx.EVT_BUTTON, self.toggle_draw) self.Bind(wx.EVT_SIZE, self.OnSize) else: bg.Sizer.Add((0, 16)) self.Sizer.SetSizeHints(self) self.Sizer.Layout()
def update(report_path, pack=True): """ Update existing report with current template files. Also creates a backup copy of the old report. """ # read original report try: orig_report = codecs.open(report_path, "r", "UTF-8") except (IOError, OSError) as exception: raise exception.__class__(lang.getstr("error.file.open", report_path)) orig_report_html = orig_report.read() orig_report.close() data = ( ("${PLANCKIAN}", 'id="FF_planckian"\s*(.*?)\s*disabled="disabled"', 0), ("${DISPLAY}", '"FF_display"\s*value="(.+?)"\s*/?>', 0), ("${INSTRUMENT}", '"FF_instrument"\s*value="(.+?)"\s*/?>', 0), ("${CORRECTION_MATRIX}", '"FF_correction_matrix"\s*value="(.+?)"\s*/?>', 0), ("${BLACKPOINT}", '"FF_blackpoint"\s*value="(.+?)"\s*/?>', 0), ("${WHITEPOINT}", '"FF_whitepoint"\s*value="(.+?)"\s*/?>', 0), ("${WHITEPOINT_NORMALIZED}", '"FF_whitepoint_normalized"\s*value="(.+?)"\s*/?>', 0), ("${PROFILE}", '"FF_profile"\s*value="(.+?)"\s*/?>', 0), ("${PROFILE_WHITEPOINT}", '"FF_profile_whitepoint"\s*value="(.+?)"\s*/?>', 0), ("${PROFILE_WHITEPOINT_NORMALIZED}", '"FF_profile_whitepoint_normalized"\s*value="(.+?)"\s*/?>', 0), ("${SIMULATION_PROFILE}", 'SIMULATION_PROFILE\s*=\s*"(.+?)"[;,]', 0), ("${TRC_GAMMA}", 'BT_1886_GAMMA\s*=\s*(.+?)[;,]', 0), ("${TRC_GAMMA}", 'TRC_GAMMA\s*=\s*(.+?)[;,]', 0), ("${TRC_GAMMA_TYPE}", 'BT_1886_GAMMA_TYPE\s*=\s*"(.+?)"[;,]', 0), ("${TRC_GAMMA_TYPE}", 'TRC_GAMMA_TYPE\s*=\s*"(.+?)"[;,]', 0), ("${TRC_OUTPUT_OFFSET}", 'TRC_OUTPUT_OFFSET\s*=\s*(.+?)[;,]', 0), ("${TRC}", 'TRC\s*=\s*"(.+?)"[;,]', 0), ("${WHITEPOINT_SIMULATION}", 'WHITEPOINT_SIMULATION\s*=\s*(.+?)[;,]', 0), ("${WHITEPOINT_SIMULATION_RELATIVE}", 'WHITEPOINT_SIMULATION_RELATIVE\s*=\s*(.+?)[;,]', 0), ("${DEVICELINK_PROFILE}", 'DEVICELINK_PROFILE\s*=\s*"(.+?)"[;,]', 0), ("${TESTCHART}", '"FF_testchart"\s*value="(.+?)"\s*/?>', 0), ("${ADAPTION}", '"FF_adaption"\s*value="(.+?)"\s*/?>', 0), ("${DATETIME}", '"FF_datetime"\s*value="(.+?)"\s*/?>', 0), ("${REF}", '"FF_data_ref"\s*value="(.+?)"\s*/?>', re.DOTALL), ("${MEASURED}", '"FF_data_in"\s*value="(.+?)"\s*/?>', re.DOTALL), ("${CAL_ENTRYCOUNT}", "CAL_ENTRYCOUNT\s*=\s*(.+?)[;,]$", re.M), ("${CAL_RGBLEVELS}", "CAL_RGBLEVELS\s*=\s*(.+?)[;,]$", re.M), ("${GRAYSCALE}", "CRITERIA_GRAYSCALE\s*=\s*(.+?)[;,]$", re.M), ("${REPORT_TYPE}", '<title>(.+?) Report', 0), # Uniformity report ("${DISPLAY}", "\u2014 (.+?) \u2014", 0), ("${DATETIME}", "\u2014 .+? \u2014 (.+?)</title>", 0), ("${ROWS}", 'rows\s*=\s*(.+?)[;,]', 0), ("${COLS}", 'cols\s*=\s*(.+?)[;,]', 0), ("${RESULTS}", 'results\s*=\s*(.+?), locus = ', 0), ("${LOCUS}", "locus\s*=\s*'([^']+)'", 0)) placeholders2data = { "${REPORT_VERSION}": version_short, "${CORRECTION_MATRIX}": "Unknown", "${ADAPTION}": "None", "${CAL_ENTRYCOUNT}": "null", "${CAL_RGBLEVELS}": "null", "${GRAYSCALE}": "null", "${BLACKPOINT}": "-1 -1 -1", "${TRC_GAMMA}": "null", "${TRC_OUTPUT_OFFSET}": "0", "${WHITEPOINT_SIMULATION}": "false", "${WHITEPOINT_SIMULATION_RELATIVE}": "false" } templatename = "report" for placeholder, pattern, flags in data: result = re.search(pattern, orig_report_html, flags) if result or not placeholders2data.get(placeholder): if (placeholder == "${TRC}" and not result and "${TRC_GAMMA}" in placeholders2data): default = "BT.1886" else: default = "" placeholders2data[placeholder] = result.groups( )[0] if result else default if result and placeholder == "${COLS}": templatename = "uniformity" # backup original report shutil.copy2(report_path, "%s.%s" % (report_path, strftime("%Y-%m-%d_%H-%M-%S"))) create(report_path, placeholders2data, pack, templatename)
def install_profile(device_id, profile, timeout=CD_CLIENT_IMPORT_DAEMON_TIMEOUT / 1000.0, logfn=None): """ Install profile for device timeout Time to allow for colord to pick up new profiles (recommended not below 2 secs) """ profile_installname = os.path.join(xdg_data_home, 'icc', os.path.basename(profile.fileName)) profile_exists = os.path.isfile(profile_installname) if (profile.fileName != profile_installname and profile_exists): if logfn: logfn("About to overwrite existing", profile_installname) profile.fileName = None if profile.ID == "\0" * 16: profile.calculateID() profile.fileName = None profile_id = "icc-" + hexlify(profile.ID) # Write profile to destination profile_installdir = os.path.dirname(profile_installname) if not os.path.isdir(profile_installdir): os.makedirs(profile_installdir) # colormgr seems to have a bug where the first attempt at importing a # specific profile can time out. This seems to be work-aroundable by # writing the profile ourself first, and then importing. if not profile.fileName or not profile_exists: if logfn: logfn("Writing", profile_installname) profile.fileName = profile_installname profile.write() cdprofile = None if Colord and not isinstance(Colord, DBusObject): client = client_connect() else: # Query colord for profile try: cdprofile = get_object_path(profile_id, "profile") except CDObjectQueryError as exception: # Profile not found pass colormgr = which("colormgr") if not colormgr: raise CDError("colormgr helper program not found") from worker import printcmdline cmd = safe_str(colormgr) if not cdprofile: # Import profile if logfn: logfn("-" * 80) logfn(lang.getstr("commandline")) args = [cmd, "import-profile", safe_str(profile.fileName)] printcmdline(args[0], args[1:], fn=logfn) if logfn: logfn("") ts = time.time() maxtries = 3 for n in range(1, maxtries + 1): if logfn: logfn("Trying to import profile, attempt %i..." % n) try: p = sp.Popen(args, stdout=sp.PIPE, stderr=sp.STDOUT) stdout, stderr = p.communicate() except Exception as exception: raise CDError(safe_str(exception)) if logfn and stdout.strip(): logfn(stdout.strip()) if p.returncode == 0 or os.path.isfile(profile_installname): if logfn: logfn("...ok") break elif logfn: logfn("...failed!") if p.returncode != 0 and not os.path.isfile(profile_installname): raise CDTimeout("Trying to import profile '%s' failed after " "%i tries." % (profile.fileName, n)) if not cdprofile: # Query colord for newly added profile for i in range(int(timeout / 1.0)): try: if Colord and not isinstance(Colord, DBusObject): cdprofile = client.find_profile_sync( profile_id, cancellable) else: cdprofile = get_object_path(profile_id, "profile") except CDObjectQueryError as exception: # Profile not found pass if cdprofile: break # Give colord time to pick up the profile sleep(1) if not cdprofile: raise CDTimeout( "Querying for profile %r returned no result for %s " "secs" % (profile_id, timeout)) errmsg = "Could not make profile %s default for device %s" % (profile_id, device_id) if Colord and not isinstance(Colord, DBusObject): # Connect to profile if not cdprofile.connect_sync(cancellable): raise CDError("Could not connect to profile") # Connect to existing device device = device_connect(client, device_id) # Add profile to device try: device.add_profile_sync(Colord.DeviceRelation.HARD, cdprofile, cancellable) except Exception as exception: # Profile may already have been added warnings.warn(safe_str(exception), Warning) # Make profile default for device if not device.make_profile_default_sync(cdprofile, cancellable): raise CDError(errmsg) else: # Find device object path device = get_object_path(device_id, "device") if logfn: logfn("-" * 80) logfn(lang.getstr("commandline")) # Add profile to device # (Ignore returncode as profile may already have been added) args = [cmd, "device-add-profile", device, cdprofile] printcmdline(args[0], args[1:], fn=logfn) if logfn: logfn("") try: p = sp.Popen(args, stdout=sp.PIPE, stderr=sp.STDOUT) stdout, stderr = p.communicate() except Exception as exception: raise CDError(safe_str(exception)) if logfn and stdout.strip(): logfn(stdout.strip()) if logfn: logfn("") logfn(lang.getstr("commandline")) # Make profile default for device args = [cmd, "device-make-profile-default", device, cdprofile] printcmdline(args[0], args[1:], fn=logfn) if logfn: logfn("") try: p = sp.Popen(args, stdout=sp.PIPE, stderr=sp.STDOUT) stdout, stderr = p.communicate() except Exception as exception: raise CDError(safe_str(exception)) else: if p.returncode != 0: raise CDError(stdout.strip() or errmsg) if logfn and stdout.strip(): logfn(stdout.strip())
def __init__(self, parent=None, handler=None, keyhandler=None, start_timer=True): BaseFrame.__init__(self, parent, wx.ID_ANY, lang.getstr("measurement.untethered"), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL, name="untetheredframe") self.SetIcons(get_icon_bundle([256, 48, 32, 16], appname)) self.sizer = wx.FlexGridSizer(2, 1, 0, 0) self.sizer.AddGrowableCol(0) self.sizer.AddGrowableRow(0) self.sizer.AddGrowableRow(1) self.panel = wx_Panel(self) self.SetSizer(self.sizer) self.sizer.Add(self.panel, 1, wx.EXPAND) self.panel.SetBackgroundColour(BGCOLOUR) panelsizer = wx.FlexGridSizer(3, 2, 8, 8) panelsizer.AddGrowableCol(0) panelsizer.AddGrowableCol(1) panelsizer.AddGrowableRow(1) self.panel.SetSizer(panelsizer) self.label_RGB = wx.StaticText(self.panel, wx.ID_ANY, " ") self.label_RGB.SetForegroundColour(FGCOLOUR) panelsizer.Add(self.label_RGB, 0, wx.TOP | wx.LEFT | wx.EXPAND, border=8) self.label_XYZ = wx.StaticText(self.panel, wx.ID_ANY, " ") self.label_XYZ.SetForegroundColour(FGCOLOUR) panelsizer.Add(self.label_XYZ, 0, wx.TOP | wx.RIGHT | wx.EXPAND, border=8) if sys.platform == "darwin": style = wx.BORDER_THEME else: style = wx.BORDER_SIMPLE self.panel_RGB = BitmapBackgroundPanel(self.panel, size=(256, 256), style=style) self.panel_RGB.scalebitmap = (True, True) self.panel_RGB.SetBitmap( getbitmap("theme/checkerboard-32x32x5-333-444")) panelsizer.Add(self.panel_RGB, 1, wx.LEFT | wx.EXPAND, border=8) self.panel_XYZ = BitmapBackgroundPanel(self.panel, size=(256, 256), style=style) self.panel_XYZ.scalebitmap = (True, True) self.panel_XYZ.SetBitmap( getbitmap("theme/checkerboard-32x32x5-333-444")) panelsizer.Add(self.panel_XYZ, 1, wx.RIGHT | wx.EXPAND, border=8) sizer = wx.BoxSizer(wx.HORIZONTAL) self.back_btn = FlatShadedButton(self.panel, bitmap=geticon(10, "back"), label="", fgcolour=FGCOLOUR) self.back_btn.Bind(wx.EVT_BUTTON, self.back_btn_handler) sizer.Add(self.back_btn, 0, wx.LEFT | wx.RIGHT, border=8) self.label_index = wx.StaticText(self.panel, wx.ID_ANY, " ") self.label_index.SetForegroundColour(FGCOLOUR) sizer.Add(self.label_index, 0, wx.ALIGN_CENTER_VERTICAL) self.next_btn = FlatShadedButton(self.panel, bitmap=geticon(10, "play"), label="", fgcolour=FGCOLOUR) self.next_btn.Bind(wx.EVT_BUTTON, self.next_btn_handler) sizer.Add(self.next_btn, 0, wx.LEFT, border=8) sizer.Add((12, 1), 1) self.measure_auto_cb = CustomCheckBox(self.panel, wx.ID_ANY, lang.getstr("auto")) self.measure_auto_cb.SetForegroundColour(FGCOLOUR) self.measure_auto_cb.Bind(wx.EVT_CHECKBOX, self.measure_auto_ctrl_handler) sizer.Add(self.measure_auto_cb, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALIGN_RIGHT) panelsizer.Add(sizer, 0, wx.BOTTOM | wx.EXPAND, border=8) sizer = wx.BoxSizer(wx.HORIZONTAL) self.measure_btn = FlatShadedButton(self.panel, bitmap=geticon(10, "play"), label=lang.getstr("measure"), fgcolour=FGCOLOUR) self.measure_btn.Bind(wx.EVT_BUTTON, self.measure_btn_handler) sizer.Add(self.measure_btn, 0, wx.RIGHT, border=6) # Sound when measuring # Needs to be stereo! self.measurement_sound = audio.Sound(get_data_path("beep.wav")) self.commit_sound = audio.Sound(get_data_path("camera_shutter.wav")) bitmap = self.get_sound_on_off_btn_bitmap() self.sound_on_off_btn = FlatShadedButton(self.panel, bitmap=bitmap, fgcolour=FGCOLOUR) self.sound_on_off_btn.SetToolTipString( lang.getstr("measurement.play_sound")) self.sound_on_off_btn.Bind(wx.EVT_BUTTON, self.measurement_play_sound_handler) sizer.Add(self.sound_on_off_btn, 0) sizer.Add((12, 1), 1) self.finish_btn = FlatShadedButton(self.panel, label=lang.getstr("finish"), fgcolour=FGCOLOUR) self.finish_btn.Bind(wx.EVT_BUTTON, self.finish_btn_handler) sizer.Add(self.finish_btn, 0, wx.RIGHT, border=8) panelsizer.Add(sizer, 0, wx.BOTTOM | wx.EXPAND, border=8) self.grid = CustomGrid(self, -1, size=(536, 256)) self.grid.DisableDragColSize() self.grid.DisableDragRowSize() self.grid.SetScrollRate(0, 5) self.grid.SetCellHighlightROPenWidth(0) self.grid.SetColLabelSize(self.grid.GetDefaultRowSize()) self.grid.SetDefaultCellAlignment(wx.ALIGN_CENTER, wx.ALIGN_CENTER) self.grid.SetRowLabelAlignment(wx.ALIGN_RIGHT, wx.ALIGN_CENTER) self.grid.draw_horizontal_grid_lines = False self.grid.draw_vertical_grid_lines = False self.grid.style = "" self.grid.CreateGrid(0, 9) self.grid.SetRowLabelSize(62) for i in range(9): if i in (3, 4): size = self.grid.GetDefaultRowSize() if i == 4: attr = wx.grid.GridCellAttr() attr.SetBackgroundColour(wx.Colour(0, 0, 0, 0)) self.grid.SetColAttr(i, attr) else: size = 62 self.grid.SetColSize(i, size) for i, label in enumerate( ["R", "G", "B", "", "", "L*", "a*", "b*", ""]): self.grid.SetColLabelValue(i, label) self.grid.SetCellHighlightPenWidth(0) self.grid.SetDefaultCellBackgroundColour( self.grid.GetLabelBackgroundColour()) font = self.grid.GetDefaultCellFont() if font.PointSize > 11: font.PointSize = 11 self.grid.SetDefaultCellFont(font) self.grid.SetSelectionMode(wx.grid.Grid.wxGridSelectRows) self.grid.EnableEditing(False) self.grid.EnableGridLines(False) self.grid.Bind(wx.grid.EVT_GRID_LABEL_LEFT_CLICK, self.grid_left_click_handler) self.grid.Bind(wx.grid.EVT_GRID_SELECT_CELL, self.grid_left_click_handler) self.sizer.Add(self.grid, 1, wx.EXPAND) self.Fit() self.SetMinSize(self.GetSize()) self.keyhandler = keyhandler self.id_to_keycode = {} if sys.platform == "darwin": # Use an accelerator table for tab, space, 0-9, A-Z, numpad, # navigation keys and processing keys keycodes = [wx.WXK_TAB, wx.WXK_SPACE] keycodes.extend(list(range(ord("0"), ord("9")))) keycodes.extend(list(range(ord("A"), ord("Z")))) keycodes.extend(numpad_keycodes) keycodes.extend(nav_keycodes) keycodes.extend(processing_keycodes) for keycode in keycodes: self.id_to_keycode[wx.Window.NewControlId()] = keycode accels = [] for id, keycode in self.id_to_keycode.items(): self.Bind(wx.EVT_MENU, self.key_handler, id=id) accels.append((wx.ACCEL_NORMAL, keycode, id)) if keycode == wx.WXK_TAB: accels.append((wx.ACCEL_SHIFT, keycode, id)) self.SetAcceleratorTable(wx.AcceleratorTable(accels)) else: self.Bind(wx.EVT_CHAR_HOOK, self.key_handler) self.Bind(wx.EVT_KEY_DOWN, self.key_handler) # Event handlers self.Bind(wx.EVT_CLOSE, self.OnClose, self) self.Bind(wx.EVT_MOVE, self.OnMove, self) self.Bind(wx.EVT_SIZE, self.OnResize, self) self.timer = wx.Timer(self) if handler: self.Bind(wx.EVT_TIMER, handler, self.timer) self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy, self) # Final initialization steps for child in self.GetAllChildren(): if (sys.platform == "win32" and sys.getwindowsversion() >= (6, ) and isinstance(child, wx.Panel)): # No need to enable double buffering under Linux and Mac OS X. # Under Windows, enabling double buffering on the panel seems # to work best to reduce flicker. child.SetDoubleBuffered(True) self.logger = get_file_logger("untethered") self._setup() self.Show() if start_timer: self.start_timer()
def parse_txt(self, txt): if not txt: return self.logger.info("%r" % txt) data_len = len(self.cgats[0].DATA) if (self.grid.GetNumberRows() < data_len): self.index = 0 self.index_max = data_len - 1 self.grid.AppendRows(data_len - self.grid.GetNumberRows()) for i in self.cgats[0].DATA: self.grid.SetRowLabelValue(i, "%i" % (i + 1)) row = self.cgats[0].DATA[i] RGB = [] for j, label in enumerate("RGB"): value = int(round(row["RGB_%s" % label] / 100.0 * 255)) self.grid.SetCellValue(row.SAMPLE_ID - 1, j, "%i" % value) RGB.append(value) self.grid.SetCellBackgroundColour(row.SAMPLE_ID - 1, 3, wx.Colour(*RGB)) if "Connecting to the instrument" in txt: self.Pulse(lang.getstr("instrument.initializing")) if "Spot read needs a calibration" in txt: self.is_measuring = False if "Spot read failed" in txt: self.last_error = txt if "Result is XYZ:" in txt: self.last_error = None if getcfg("measurement.play_sound"): self.measurement_sound.safe_play() # Result is XYZ: d.dddddd d.dddddd d.dddddd, D50 Lab: d.dddddd d.dddddd d.dddddd XYZ = re.search( "XYZ:\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)\s+(-?\d+(?:\.\d+)?)", txt) if not XYZ: return XYZ = [float(v) for v in XYZ.groups()] row = self.cgats[0].DATA[self.index] if (row["RGB_R"] == 100 and row["RGB_G"] == 100 and row["RGB_B"] == 100): # White if XYZ[1] > 0: self.cgats[0].add_keyword("LUMINANCE_XYZ_CDM2", "%.6f %.6f %.6f" % tuple(XYZ)) self.white_XYZ = XYZ Lab1 = colormath.XYZ2Lab(*self.last_XYZ) Lab2 = colormath.XYZ2Lab(*XYZ) delta = colormath.delta(*Lab1 + Lab2) if debug or test or verbose > 1: safe_print("Last recorded Lab: %.4f %.4f %.4f" % Lab1) safe_print("Current Lab: %.4f %.4f %.4f" % Lab2) safe_print("Delta E to last recorded Lab: %.4f" % delta["E"]) safe_print("Abs. delta L to last recorded Lab: %.4f" % abs(delta["L"])) safe_print("Abs. delta C to last recorded Lab: %.4f" % abs(delta["C"])) if (delta["E"] > getcfg("untethered.min_delta") or (abs(delta["L"]) > getcfg("untethered.min_delta.lightness") and abs(delta["C"]) < getcfg("untethered.max_delta.chroma"))): self.measure_count += 1 if self.measure_count == 2: if getcfg("measurement.play_sound"): self.commit_sound.safe_play() self.measure_count = 0 # Reset row label self.grid.SetRowLabelValue(self.index, "%i" % (self.index + 1)) # Update CGATS query = self.cgats[0].queryi({ "RGB_R": row["RGB_R"], "RGB_G": row["RGB_G"], "RGB_B": row["RGB_B"] }) for i in query: index = query[i].SAMPLE_ID - 1 if index not in self.measured: self.measured.append(index) if index == self.index + 1: # Increment the index if we have consecutive patches self.index = index query[i]["XYZ_X"], query[i]["XYZ_Y"], query[i][ "XYZ_Z"] = XYZ if getcfg("untethered.measure.auto"): self.show_RGB(False, False) self.show_XYZ() Lab, color = self.get_Lab_RGB() for i in query: row = query[i] self.grid.SetCellBackgroundColour( query[i].SAMPLE_ID - 1, 4, wx.Colour(*color)) for j in range(3): self.grid.SetCellValue(query[i].SAMPLE_ID - 1, 5 + j, "%.2f" % Lab[j]) self.grid.MakeCellVisible(self.index, 0) self.grid.ForceRefresh() if len(self.measured) == data_len: self.finished = True self.finish_btn.Enable() else: # Jump to the next or previous unmeasured patch, if any index = self.index for i in range(self.index + 1, data_len): if (getcfg("untethered.measure.auto") or not i in self.measured): self.index = i break if self.index == index: for i in range(self.index - 1, -1, -1): if not i in self.measured: self.index = i break if self.index != index: # Mark the row containing the next/previous patch self.grid.SetRowLabelValue( self.index, "\u25ba %i" % (self.index + 1)) self.grid.MakeCellVisible(self.index, 0) if "key to take a reading" in txt and not self.last_error: if getcfg("untethered.measure.auto") and self.is_measuring: if not self.finished and self.keepGoing: self.measure() else: self.enable_btns() else: show_XYZ = self.index in self.measured delay = getcfg("untethered.measure.manual.delay") * 1000 wx.CallLater(delay, self.show_RGB, not show_XYZ) if show_XYZ: wx.CallLater(delay, self.show_XYZ) wx.CallLater(delay, self.enable_btns)
def Pulse(self, msg=""): if msg == lang.getstr("instrument.initializing"): self.label_RGB.SetLabel(msg) return self.keepGoing, False
def extract_device_gray_primaries(ti3, gray=True, logfn=None, include_neutrals=False, neutrals_ab_threshold=0.1): """ Extract gray or primaries into new TI3 Return extracted ti3, extracted RGB to XYZ mapping and remaining RGB to XYZ """ filename = ti3.filename ti3 = ti3.queryi1("DATA") ti3.filename = filename ti3_extracted = CGATS.CGATS("""CTI3 DEVICE_CLASS "DISPLAY" COLOR_REP "RGB_XYZ" BEGIN_DATA_FORMAT END_DATA_FORMAT BEGIN_DATA END_DATA""")[0] ti3_extracted.DATA_FORMAT.update(ti3.DATA_FORMAT) subset = [(100.0, 100.0, 100.0), (0.0, 0.0, 0.0)] if not gray: subset.extend([(100.0, 0.0, 0.0), (0.0, 100.0, 0.0), (0.0, 0.0, 100.0), (50.0, 50.0, 50.0)]) if logfn: logfn("Extracting neutrals and primaries from %s" % ti3.filename) else: if logfn: logfn("Extracting neutrals from %s" % ti3.filename) RGB_XYZ_extracted = OrderedDict() RGB_XYZ_remaining = OrderedDict() dupes = {} if include_neutrals: white = ti3.get_white_cie("XYZ") str_thresh = str(neutrals_ab_threshold) round_digits = len(str_thresh[str_thresh.find(".") + 1:]) for i, item in ti3.DATA.items(): if not i: # Check if fields are missing for prefix in ("RGB", "XYZ"): for suffix in prefix: key = "%s_%s" % (prefix, suffix) if not key in item: raise Error( lang.getstr("error.testchart.missing_fields", (ti3.filename, key))) RGB = (item["RGB_R"], item["RGB_G"], item["RGB_B"]) XYZ = (item["XYZ_X"], item["XYZ_Y"], item["XYZ_Z"]) for RGB_XYZ in (RGB_XYZ_extracted, RGB_XYZ_remaining): if RGB in RGB_XYZ: if RGB != (100.0, 100.0, 100.0): # Add to existing values for averaging later # if it's not white (all other readings are scaled to the # white Y by dispread, so we don't alter it. Note that it's # always the first encountered white that will have Y = 100, # even if subsequent white readings may be higher) XYZ = tuple(RGB_XYZ[RGB][i] + XYZ[i] for i in range(3)) if not RGB in dupes: dupes[RGB] = 1.0 dupes[RGB] += 1.0 elif RGB in subset: # We have white already, remove it from the subset so any # additional white readings we encounter are ignored subset.remove(RGB) if ((gray and (item["RGB_R"] == item["RGB_G"] == item["RGB_B"] or (include_neutrals and all( round(abs(v), round_digits) <= neutrals_ab_threshold for v in colormath.XYZ2Lab(item["XYZ_X"], item["XYZ_Y"], item["XYZ_Z"], whitepoint=white)[1:]))) and not RGB in [(100.0, 100.0, 100.0), (0.0, 0.0, 0.0)]) or RGB in subset): ti3_extracted.DATA.add_data(item) RGB_XYZ_extracted[RGB] = XYZ elif not RGB in [(100.0, 100.0, 100.0), (0.0, 0.0, 0.0)]: RGB_XYZ_remaining[RGB] = XYZ for RGB, count in dupes.items(): for RGB_XYZ in (RGB_XYZ_extracted, RGB_XYZ_remaining): if RGB in RGB_XYZ: # Average values XYZ = tuple(RGB_XYZ[RGB][i] / count for i in range(3)) RGB_XYZ[RGB] = XYZ return ti3_extracted, RGB_XYZ_extracted, RGB_XYZ_remaining
def extract_cal_from_profile(profile, out_cal_path=None, raise_on_missing_cal=True, prefer_cal=False): """ Extract calibration from 'targ' tag in profile or vcgt as fallback """ white = False # Check if calibration is included in TI3 targ = profile.tags.get("targ", profile.tags.get("CIED")) if isinstance(targ, ICCP.Text): cal = extract_cal_from_ti3(targ) if cal: check = cal get_cgats = CGATS.CGATS arg = cal else: cal = None if not cal: # Convert calibration information from embedded WCS profile # (if present) to VideCardFormulaType if the latter is not present if (isinstance(profile.tags.get("MS00"), ICCP.WcsProfilesTagType) and not "vcgt" in profile.tags): profile.tags["vcgt"] = profile.tags["MS00"].get_vcgt() # Get the calibration from profile vcgt check = isinstance(profile.tags.get("vcgt"), ICCP.VideoCardGammaType) get_cgats = vcgt_to_cal arg = profile if not check: if raise_on_missing_cal: raise Error(lang.getstr("profile.no_vcgt")) else: return False else: try: cgats = get_cgats(arg) except (IOError, CGATS.CGATSError) as exception: raise Error(lang.getstr("cal_extraction_failed")) if (cal and not prefer_cal and isinstance(profile.tags.get("vcgt"), ICCP.VideoCardGammaType)): # When vcgt is nonlinear, prefer it # Check for video levels encoding if cgats.queryv1("TV_OUTPUT_ENCODING") == "YES": black, white = (16, 235) else: output_enc = cgats.queryv1("OUTPUT_ENCODING") if output_enc: try: black, white = (float(v) for v in output_enc.split()) except (TypeError, ValueError): white = False cgats = vcgt_to_cal(profile) if white and (black, white) != (0, 255): safe_print("Need to un-scale vcgt from video levels (%s..%s)" % (black, white)) # Need to un-scale video levels data = cgats.queryv1("DATA") if data: safe_print("Un-scaling vcgt from video levels (%s..%s)" % (black, white)) encoding_mismatch = False # For video encoding the extra bits of # precision are created by bit shifting rather # than scaling, so we need to scale the fp # value to account for this oldmin = (black / 256.0) * (65536 / 65535.) oldmax = (white / 256.0) * (65536 / 65535.) for entry in data.values(): for column in "RGB": v_old = entry["RGB_" + column] lvl = round(v_old * (65535 / 65536.) * 256, 2) if lvl < round(black, 2) or lvl > round(white, 2): # Can't be right. Metadata says it's video encoded, # but clearly exceeds the encoding range. safe_print("Warning: Metadata claims video levels " "(%s..%s) but vcgt value %s exceeds " "encoding range. Using values as-is." % (round(black, 2), round(white, 2), lvl)) encoding_mismatch = True break v_new = colormath.convert_range( v_old, oldmin, oldmax, 0, 1) entry["RGB_" + column] = min(max(v_new, 0), 1) if encoding_mismatch: break if encoding_mismatch: cgats = vcgt_to_cal(profile) # Add video levels hint to CGATS elif (black, white) == (16, 235): cgats[0].add_keyword("TV_OUTPUT_ENCODING", "YES") else: cgats[0].add_keyword( "OUTPUT_ENCODING", " ".join(str(v) for v in (black, white))) else: safe_print("Warning - no un-scaling applied - no " "calibration data!") if out_cal_path: cgats.write(out_cal_path) return cgats
def __init__(self, parent=None, handler=None, keyhandler=None, start_timer=True, rows=None, cols=None): if not rows: rows = getcfg("uniformity.rows") if not cols: cols = getcfg("uniformity.cols") BaseFrame.__init__(self, parent, wx.ID_ANY, lang.getstr("report.uniformity"), style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL, name="displayuniformityframe") self.SetIcons(get_icon_bundle([256, 48, 32, 16], appname)) self.SetBackgroundColour(BGCOLOUR) self.sizer = wx.GridSizer(rows, cols, 0, 0) self.SetSizer(self.sizer) self.rows = rows self.cols = cols self.colors = (wx.WHITE, wx.Colour(192, 192, 192), wx.Colour(128, 128, 128), wx.Colour(64, 64, 64)) self.labels = {} self.panels = [] self.buttons = [] for index in range(rows * cols): panel = wx_Panel(self, style=wx.BORDER_SIMPLE) panel.SetBackgroundColour(BGCOLOUR) sizer = wx.BoxSizer(wx.VERTICAL) panel.SetSizer(sizer) self.panels.append(panel) button = FlatShadedNumberedButton( panel, label=lang.getstr("measure"), bitmap=getbitmap("theme/icons/10x10/record"), index=index) button.Bind(wx.EVT_BUTTON, self.measure) self.buttons.append(button) label = wx.StaticText(panel) label.SetForegroundColour(wx.WHITE) self.labels[index] = label sizer.Add(label, 1, wx.ALIGN_CENTER) sizer.Add(button, 0, wx.ALIGN_BOTTOM | wx.ALIGN_CENTER | wx.BOTTOM | wx.LEFT | wx.RIGHT, border=8) self.sizer.Add(panel, 1, wx.EXPAND) self.disable_buttons() self.keyhandler = keyhandler self.id_to_keycode = {} if sys.platform == "darwin": # Use an accelerator table for tab, space, 0-9, A-Z, numpad, # navigation keys and processing keys keycodes = [wx.WXK_TAB, wx.WXK_SPACE] keycodes.extend(list(range(ord("0"), ord("9")))) keycodes.extend(list(range(ord("A"), ord("Z")))) keycodes.extend(numpad_keycodes) keycodes.extend(nav_keycodes) keycodes.extend(processing_keycodes) for keycode in keycodes: self.id_to_keycode[wx.Window.NewControlId()] = keycode accels = [] for id, keycode in self.id_to_keycode.items(): self.Bind(wx.EVT_MENU, self.key_handler, id=id) accels.append((wx.ACCEL_NORMAL, keycode, id)) if keycode == wx.WXK_TAB: accels.append((wx.ACCEL_SHIFT, keycode, id)) self.SetAcceleratorTable(wx.AcceleratorTable(accels)) else: self.Bind(wx.EVT_CHAR_HOOK, self.key_handler) # Event handlers self.Bind(wx.EVT_CLOSE, self.OnClose, self) self.Bind(wx.EVT_MOVE, self.OnMove, self) self.timer = wx.Timer(self) if handler: self.Bind(wx.EVT_TIMER, handler, self.timer) self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy, self) # Final initialization steps self.logger = get_file_logger("uniformity") self._setup() self.Show() if start_timer: self.start_timer()
def vrmlfile2x3dfile(vrmlpath=None, x3dpath=None, html=True, embed=False, view=False, force=False, cache=True, worker=None, gui=True): """ Convert VRML to HTML. Output is written to <vrmlfilename>.x3d.html unless you set x3dpath to desired output path, or False to be prompted for an output path. """ while not vrmlpath or not os.path.isfile(vrmlpath): if not gui: if not vrmlpath or vrmlpath.startswith("--"): safe_print("No filename given.") else: safe_print("%r is not a file." % vrmlpath) return False if not wx.GetApp(): app = BaseApp(0) defaultDir, defaultFile = config.get_verified_path("last_vrml_path") dlg = wx.FileDialog(None, lang.getstr("file.select"), defaultDir=defaultDir, defaultFile=defaultFile, wildcard=lang.getstr("filetype.vrml") + "|*.vrml;*.vrml.gz;*.wrl.gz;*.wrl;*.wrz", style=wx.FD_OPEN | wx.FD_FILE_MUST_EXIST) dlg.Center(wx.BOTH) result = dlg.ShowModal() vrmlpath = dlg.GetPath() dlg.Destroy() if result != wx.ID_OK: return config.setcfg("last_vrml_path", vrmlpath) config.writecfg(module="VRML-to-X3D-converter", options=("last_vrml_path", )) filename, ext = os.path.splitext(vrmlpath) if x3dpath is None: x3dpath = filename + ".x3d" if x3dpath: dirname = os.path.dirname(x3dpath) while not x3dpath or not waccess(dirname, os.W_OK): if not gui: if not x3dpath: safe_print("No HTML output filename given.") else: safe_print("%r is not writable." % dirname) return False if not wx.GetApp(): app = BaseApp(0) if x3dpath: defaultDir, defaultFile = os.path.split(x3dpath) else: defaultFile = os.path.basename(filename) + ".x3d" dlg = wx.FileDialog(None, lang.getstr("error.access_denied.write", dirname), defaultDir=defaultDir, defaultFile=defaultFile, wildcard=lang.getstr("filetype.x3d") + "|*.x3d", style=wx.SAVE | wx.FD_OVERWRITE_PROMPT) dlg.Center(wx.BOTH) result = dlg.ShowModal() dlg.Destroy() if result != wx.ID_OK: return x3dpath = dlg.GetPath() dirname = os.path.dirname(x3dpath) vrmlpath, x3dpath = [safe_unicode(path) for path in (vrmlpath, x3dpath)] if sys.platform == "win32": vrmlpath = make_win32_compatible_long_path(vrmlpath) x3dpath = make_win32_compatible_long_path(x3dpath) if html: finalpath = x3dpath + ".html" if sys.platform == "win32": finalpath = make_win32_compatible_long_path(finalpath) x3dpath = finalpath[:-5] else: finalpath = x3dpath if worker: worker.clear_cmd_output() worker.start( lambda result: show_result_dialog(result, wx.GetApp().GetTopWindow()) if isinstance(result, Exception ) else result and view and launch_file(finalpath), x3dom.vrmlfile2x3dfile, wargs=(vrmlpath, x3dpath, html, embed, force, cache, worker), progress_title=lang.getstr("vrml_to_x3d_converter"), progress_start=1, resume=worker.progress_wnd and worker.progress_wnd.IsShownOnScreen(), fancy=False) else: result = x3dom.vrmlfile2x3dfile(vrmlpath, x3dpath, html, embed, force, cache, None) if not isinstance(result, Exception) and result: if view: launch_file(finalpath) else: return False return True