-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #34 from sunjerry019/cli
Add a CLI Application
- Loading branch information
Showing
15 changed files
with
366 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
@echo off | ||
call %ProgramData%\Anaconda3\Scripts\activate.bat nanosquared | ||
python ./src/cli-app/m2-app.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
|
||
setuptools.setup( | ||
name="nanosquared", | ||
version="0.1.1-alpha", | ||
version="0.2", | ||
author="Yudong Sun", | ||
author_email="[email protected]", | ||
description="Automated M-Squared Measurement", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import os, sys | ||
import distutils.util | ||
|
||
# https://stackoverflow.com/a/287944/3211506 | ||
class bcolors: | ||
HEADER = '\033[95m' | ||
OKBLUE = '\033[94m' | ||
OKCYAN = '\033[96m' | ||
OKGREEN = '\033[92m' | ||
WARNING = '\033[93m' | ||
FAIL = '\033[91m' | ||
ENDC = '\033[0m' | ||
BOLD = '\033[1m' | ||
UNDERLINE = '\033[4m' | ||
|
||
class CLI(): | ||
COLORS = bcolors | ||
GAP = f"\033[95m==>>>\033[0m " | ||
|
||
def __init__(self) -> None: | ||
pass | ||
|
||
@staticmethod | ||
def clear_screen(): | ||
os.system('cls' if os.name == 'nt' else 'clear') | ||
|
||
@staticmethod | ||
def print_sep(): | ||
print("======================") | ||
|
||
@staticmethod | ||
def presskeycont(): | ||
input("Press Enter to continue...") | ||
return | ||
|
||
@staticmethod | ||
def getPositiveNonZeroFloat(question, default = None) -> float: | ||
|
||
prompt = f"[Default = {default}]" if default is not None else "" | ||
|
||
while True: | ||
try: | ||
resp = input(CLI.GAP + question + " " + prompt + " > ").strip() | ||
if default is not None and resp == '': | ||
return default | ||
else: | ||
resp = float(resp) | ||
if resp <= 0: | ||
raise ValueError | ||
return resp | ||
except ValueError: | ||
print("ERROR: Please respond with a positive number/float.") | ||
except EOFError: | ||
print("Encountered EOF, exiting...") | ||
sys.exit() | ||
|
||
@staticmethod | ||
def getIntWithLimit(question, default = None, lowerlimit: int = 1) -> int: | ||
"""Gets an integer that is no lower than the `lowerlimit` | ||
Parameters | ||
---------- | ||
question : str | ||
Question to ask | ||
default : int, optional | ||
Default value, by default None | ||
lowerlimit : int, optional | ||
Lowest acceptable integer, by default 1 | ||
Returns | ||
------- | ||
int | ||
Received input | ||
""" | ||
|
||
prompt = f"[Default = {default}]" if default is not None else "" | ||
|
||
while True: | ||
try: | ||
resp = input(CLI.GAP + question + " " + prompt + " > ").strip() | ||
if default is not None and resp == '': | ||
return default | ||
else: | ||
resp = int(resp) | ||
if resp <= lowerlimit: | ||
raise ValueError | ||
return resp | ||
except ValueError: | ||
print(f"ERROR: Please respond with an integer that is at least {lowerlimit}") | ||
except EOFError: | ||
print("Encountered EOF, exiting...") | ||
sys.exit() | ||
|
||
@staticmethod | ||
def options(question, options, default): | ||
assert (default in options) or (default is None), "ERROR: default not in options" | ||
|
||
prompt = f"[Default = {default}]" if default is not None else "" | ||
|
||
while True: | ||
try: | ||
resp = input(CLI.GAP + question + " " + prompt + " > ").strip().lower() | ||
if default is not None and resp == '': | ||
return default | ||
else: | ||
if resp not in options: | ||
raise ValueError | ||
return resp | ||
except ValueError: | ||
print("ERROR: Please respond with one of the options.") | ||
except EOFError: | ||
print("Encountered EOF, exiting...") | ||
sys.exit() | ||
|
||
# https://gist.github.com/garrettdreyfus/8153571?permalink_comment_id=3263216#gistcomment-3263216 | ||
@staticmethod | ||
def whats_it_gonna_be_boy(question, default='no') -> bool: | ||
if default is None: | ||
prompt = " [y/n]" | ||
elif default == 'yes': | ||
prompt = " [Y/n]" | ||
elif default == 'no': | ||
prompt = " [y/N]" | ||
else: | ||
raise ValueError(f"Unknown setting '{default}' for default.") | ||
|
||
while True: | ||
try: | ||
resp = input(CLI.GAP + question + prompt + " > ").strip().lower() | ||
if default is not None and resp == '': | ||
return (default == 'yes') | ||
else: | ||
return bool(distutils.util.strtobool(resp)) | ||
except ValueError: | ||
print("ERROR: Please respond with 'yes' or 'no' (or 'y' or 'n').\n") | ||
except EOFError: | ||
print("Encountered EOF, exiting...") | ||
sys.exit() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Made 2022, Sun Yudong | ||
# yudong.sun [at] mpq.mpg.de / yudong [at] outlook.de | ||
|
||
# Possible Improvements | ||
# - Different fitting methods | ||
# - Provide option to choose which way the beam is coming in | ||
# - Some proper way of breaking operations | ||
|
||
from email.policy import default | ||
import os, sys | ||
from matplotlib.style import available | ||
import serial.tools.list_ports | ||
|
||
from cli import CLI | ||
|
||
try: | ||
import nanosquared | ||
print("Using pip-installed version (may not be up-to-date).") | ||
print("If you have recently updated the repository, do `pip install .` to update the installed version with the one in the repository.") | ||
except ModuleNotFoundError as e: | ||
base_dir = os.path.dirname(os.path.realpath(__file__)) | ||
root_dir = os.path.abspath(os.path.join(base_dir, "..")) | ||
sys.path.insert(0, root_dir) | ||
|
||
import nanosquared | ||
|
||
# https://asciiflow.com/ | ||
|
||
def setup(): | ||
while True: | ||
print(f"{CLI.COLORS.HEADER}==== SETUP ===={CLI.COLORS.ENDC}") | ||
print("\nIn the following questions, pressing Enter will enter the default option, which is indicated in capital letters.") | ||
print("\nIn development mode, no actual devices are required.\nAll function calls will therefore be simulated.") | ||
print("Use this mode if you only want to fit.") | ||
devMode = CLI.whats_it_gonna_be_boy("Run in development mode?") | ||
|
||
print("\nNanoScan and WinCamD Beam Profilers are supported. \nyes = NanoScan, no = WinCamD") | ||
useNanoScan = CLI.whats_it_gonna_be_boy("Use NanoScan?", default = 'yes') | ||
|
||
ports = serial.tools.list_ports.comports() | ||
|
||
print("\nAvailable COM Ports") | ||
available_ports = [] | ||
default_port = None | ||
for port, desc, hwid in sorted(ports): | ||
port_num = port[3:] | ||
available_ports.append(port_num) | ||
|
||
if "communication" in desc.lower() or "comm" in desc.lower(): | ||
default_port = port_num | ||
|
||
print("| {}: {} [{}]".format(port, desc, hwid)) | ||
|
||
if len(available_ports) < 1: | ||
print("Error: No COM ports available. Exiting...") | ||
sys.exit() | ||
|
||
if default_port is None: | ||
default_port = available_ports[0] | ||
|
||
comPort = int(CLI.options("Which COM Port for stage?", options = available_ports, default = default_port)) | ||
|
||
print(f"{CLI.COLORS.HEADER}Obtained:\n--- devMode : {devMode}\n--- Profiler : {'NanoScan' if useNanoScan else 'WinCamD'}\n--- COM Port : COM{comPort}{CLI.COLORS.ENDC}") | ||
confirm = CLI.whats_it_gonna_be_boy(f"Proceed?", default = "yes") | ||
|
||
if confirm: | ||
break | ||
CLI.clear_screen() | ||
|
||
return devMode, useNanoScan, comPort | ||
|
||
CLI.clear_screen() | ||
print(f""" | ||
┌──────────────────────────────────────┐ | ||
│ │ | ||
│ {CLI.COLORS.OKCYAN}Welcome to M² Measurement Wizard{CLI.COLORS.ENDC} │ | ||
│ │ | ||
│ Made 2021-2022, Yudong Sun │ | ||
│ github.com/sunjerry019/nanosquared │ | ||
│ │ | ||
└──────────────────────────────────────┘ | ||
""") | ||
|
||
devMode, useNanoScan, comPort = setup() | ||
|
||
cfg = { "port" : f"COM{comPort}" } | ||
|
||
cam = nanosquared.cameras.nanoscan.NanoScan if useNanoScan else nanosquared.cameras.wincamd.WinCamD | ||
|
||
print(f"{CLI.COLORS.OKGREEN}Got it! Initialising...{CLI.COLORS.ENDC}") | ||
with cam(devMode = devMode) as n: | ||
with nanosquared.stage.controller.GSC01(devMode = devMode, devConfig = cfg) as s: | ||
with nanosquared.measurement.measure.Measurement(devMode = devMode, camera = n, controller = s) as M: | ||
print(f"{CLI.COLORS.OKGREEN}Initialisation done!{CLI.COLORS.ENDC}") | ||
|
||
print("") | ||
CLI.print_sep() | ||
print(f"{CLI.COLORS.FAIL}IMPT{CLI.COLORS.ENDC}\n{CLI.COLORS.FAIL}IMPT{CLI.COLORS.ENDC}: If you happen to quit halfway through, use the Task Manager > Processes to ensure that no NanoScanII.exe instances are running before restarting this wizard.\n{CLI.COLORS.FAIL}IMPT{CLI.COLORS.ENDC}") | ||
CLI.print_sep() | ||
print("") | ||
|
||
ic = CLI.whats_it_gonna_be_boy("Launch Interactive Console?") | ||
|
||
def launchInteractive(): | ||
CLI.print_sep() | ||
print(f"\n{CLI.COLORS.OKCYAN}with nanosquared.measurement.measure.Measurement(devMode = {devMode}) as M{CLI.COLORS.ENDC}") | ||
import code; code.interact(local=locals()) | ||
|
||
if ic: | ||
launchInteractive() | ||
else: | ||
if not devMode: | ||
print("Assuming you want to take a measurement...") | ||
while True: | ||
while True: | ||
wavelength = CLI.getPositiveNonZeroFloat("Laser Wavelength (nm) ?") | ||
precision = CLI.getIntWithLimit("Precision of search? (pulses) ?", default = 10, lowerlimit = 2) | ||
other = input(CLI.GAP + "Other metadata > ") | ||
|
||
print(f"{CLI.COLORS.HEADER}Obtained:\n--- Wavelength : {wavelength} nm\n--- Precision : {precision} pps\n--- Other Metadata : {other}{CLI.COLORS.ENDC}") | ||
confirm = CLI.whats_it_gonna_be_boy(f"Proceed?", default = "yes") | ||
|
||
if confirm: | ||
break | ||
|
||
meta = { | ||
"Wavelength" : f"{wavelength} nm", | ||
"Precision (pps)" : precision, | ||
"Metadata" : other | ||
} | ||
M.take_measurements(precision = precision, metadata = meta) | ||
|
||
print(f"{CLI.COLORS.OKGREEN}Done!{CLI.COLORS.ENDC}") | ||
|
||
print(f"{CLI.COLORS.OKGREEN}Fitting data (X-Axis)...{CLI.COLORS.ENDC}") | ||
res = M.fit_data(axis = M.camera.AXES.X, wavelength = wavelength) | ||
print(f"{CLI.COLORS.OKGREEN}=== X-Axis ==={CLI.COLORS.ENDC}") | ||
print(f"{CLI.COLORS.OKGREEN}=== Fit Result{CLI.COLORS.ENDC}: {res}") | ||
print(f"{CLI.COLORS.OKGREEN}=== M-squared{CLI.COLORS.ENDC} : {M.fitter.m_squared}") | ||
fig, ax = M.fitter.getPlotOfFit() | ||
fig.show() | ||
|
||
CLI.presskeycont() | ||
|
||
print(f"{CLI.COLORS.OKGREEN}Fitting data (Y-Axis)...{CLI.COLORS.ENDC}") | ||
res = M.fit_data(axis = M.camera.AXES.Y, wavelength = wavelength) # Use defaults (same as above) | ||
print(f"{CLI.COLORS.OKGREEN}=== Y-Axis ==={CLI.COLORS.ENDC}") | ||
print(f"{CLI.COLORS.OKGREEN}=== Fit Result{CLI.COLORS.ENDC}: {res}") | ||
print(f"{CLI.COLORS.OKGREEN}=== M-squared{CLI.COLORS.ENDC} : {M.fitter.m_squared}") | ||
fig, ax = M.fitter.getPlotOfFit() | ||
fig.show() | ||
|
||
print(f"{CLI.COLORS.OKGREEN}All Done!{CLI.COLORS.ENDC}") | ||
|
||
ic2 = CLI.whats_it_gonna_be_boy("Launch Interactive Console?") | ||
if ic2: | ||
launchInteractive() | ||
|
||
anothermeasurement = CLI.whats_it_gonna_be_boy("Take another measurement?") | ||
if not anothermeasurement: | ||
break | ||
else: | ||
print("Assuming you want to fit...") | ||
while True: | ||
while True: | ||
wavelength = CLI.getPositiveNonZeroFloat("Laser Wavelength (nm) ?") | ||
|
||
print(f"{CLI.COLORS.HEADER}Obtained:\n--- Wavelength : {wavelength} nm{CLI.COLORS.ENDC}") | ||
confirm = CLI.whats_it_gonna_be_boy(f"Proceed?", default = "yes") | ||
|
||
if confirm: | ||
break | ||
|
||
while True: | ||
try: | ||
filename = input(CLI.GAP + "Filename > ") | ||
M.read_from_file(filename = filename) | ||
break | ||
except OSError as e: | ||
print(f"OSError: {e}. Try again.") | ||
|
||
print(f"{CLI.COLORS.OKGREEN}Fitting data (X-Axis)...{CLI.COLORS.ENDC}") | ||
res = M.fit_data(axis = M.camera.AXES.X, wavelength = wavelength) | ||
print(f"{CLI.COLORS.OKGREEN}=== X-Axis ==={CLI.COLORS.ENDC}") | ||
print(f"{CLI.COLORS.OKGREEN}=== Fit Result{CLI.COLORS.ENDC}: {res}") | ||
print(f"{CLI.COLORS.OKGREEN}=== M-squared{CLI.COLORS.ENDC} : {M.fitter.m_squared}") | ||
fig, ax = M.fitter.getPlotOfFit() | ||
fig.show() | ||
|
||
CLI.presskeycont() | ||
|
||
print(f"{CLI.COLORS.OKGREEN}Fitting data (Y-Axis)...{CLI.COLORS.ENDC}") | ||
res = M.fit_data(axis = M.camera.AXES.Y, wavelength = wavelength) # Use defaults (same as above) | ||
print(f"{CLI.COLORS.OKGREEN}=== Y-Axis ==={CLI.COLORS.ENDC}") | ||
print(f"{CLI.COLORS.OKGREEN}=== Fit Result{CLI.COLORS.ENDC}: {res}") | ||
print(f"{CLI.COLORS.OKGREEN}=== M-squared{CLI.COLORS.ENDC} : {M.fitter.m_squared}") | ||
fig, ax = M.fitter.getPlotOfFit() | ||
fig.show() | ||
|
||
|
||
print(f"{CLI.COLORS.OKGREEN}All Done!{CLI.COLORS.ENDC}") | ||
|
||
ic2 = CLI.whats_it_gonna_be_boy("Launch Interactive Console?") | ||
if ic2: | ||
launchInteractive() | ||
|
||
anotherfit = CLI.whats_it_gonna_be_boy("Fit another?") | ||
if not anotherfit: | ||
break |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.