From fff9f24c4eb4f204a45dd191a90a08d9fcfa810a Mon Sep 17 00:00:00 2001 From: Andy Wang Date: Tue, 19 May 2026 13:27:33 -0700 Subject: [PATCH] implemented hfd v curve autofocus with new endpoints --- .app.py.un~ | Bin 0 -> 1480 bytes app.py | 3 + app.py~ | 532 ++++++++++++++++++++++++++++++++ evora/.debug.py.un~ | Bin 0 -> 992 bytes evora/bin/Activate.ps1 | 247 +++++++++++++++ evora/bin/activate | 63 ++++ evora/bin/activate.csh | 26 ++ evora/bin/activate.fish | 69 +++++ evora/bin/dotenv | 8 + evora/bin/f2py | 8 + evora/bin/fits2bitmap | 8 + evora/bin/fitscheck | 8 + evora/bin/fitsdiff | 8 + evora/bin/fitsheader | 8 + evora/bin/fitsinfo | 8 + evora/bin/flask | 8 + evora/bin/fonttools | 8 + evora/bin/gunicorn | 8 + evora/bin/gunicornc | 8 + evora/bin/normalizer | 8 + evora/bin/numpy-config | 8 + evora/bin/pip | 8 + evora/bin/pip3 | 8 + evora/bin/pip3.11 | 8 + evora/bin/pyftmerge | 8 + evora/bin/pyftsubset | 8 + evora/bin/python | 1 + evora/bin/python3 | 1 + evora/bin/python3.11 | 1 + evora/bin/samp_hub | 8 + evora/bin/showtable | 8 + evora/bin/showtable-astropy | 8 + evora/bin/ttx | 8 + evora/bin/volint | 8 + evora/bin/wcslint | 8 + evora/debug.py | 2 +- evora/debug.py~ | 1 + evora/pyvenv.cfg | 5 + evora/share/man/man1/ttx.1 | 225 ++++++++++++++ new_autofocus/.__init__.py.un~ | Bin 0 -> 523 bytes new_autofocus/.endpoints.py.un~ | Bin 0 -> 523 bytes new_autofocus/.routine.py.un~ | Bin 0 -> 2285 bytes new_autofocus/__init__.py | 5 + new_autofocus/endpoints.py | 59 ++++ new_autofocus/routine.py | 93 ++++++ new_autofocus/routine.py~ | 118 +++++++ 46 files changed, 1642 insertions(+), 1 deletion(-) create mode 100644 .app.py.un~ create mode 100644 app.py~ create mode 100644 evora/.debug.py.un~ create mode 100644 evora/bin/Activate.ps1 create mode 100644 evora/bin/activate create mode 100644 evora/bin/activate.csh create mode 100644 evora/bin/activate.fish create mode 100755 evora/bin/dotenv create mode 100755 evora/bin/f2py create mode 100755 evora/bin/fits2bitmap create mode 100755 evora/bin/fitscheck create mode 100755 evora/bin/fitsdiff create mode 100755 evora/bin/fitsheader create mode 100755 evora/bin/fitsinfo create mode 100755 evora/bin/flask create mode 100755 evora/bin/fonttools create mode 100755 evora/bin/gunicorn create mode 100755 evora/bin/gunicornc create mode 100755 evora/bin/normalizer create mode 100755 evora/bin/numpy-config create mode 100755 evora/bin/pip create mode 100755 evora/bin/pip3 create mode 100755 evora/bin/pip3.11 create mode 100755 evora/bin/pyftmerge create mode 100755 evora/bin/pyftsubset create mode 120000 evora/bin/python create mode 120000 evora/bin/python3 create mode 120000 evora/bin/python3.11 create mode 100755 evora/bin/samp_hub create mode 100755 evora/bin/showtable create mode 100755 evora/bin/showtable-astropy create mode 100755 evora/bin/ttx create mode 100755 evora/bin/volint create mode 100755 evora/bin/wcslint create mode 100644 evora/debug.py~ create mode 100644 evora/pyvenv.cfg create mode 100644 evora/share/man/man1/ttx.1 create mode 100644 new_autofocus/.__init__.py.un~ create mode 100644 new_autofocus/.endpoints.py.un~ create mode 100644 new_autofocus/.routine.py.un~ create mode 100644 new_autofocus/__init__.py create mode 100644 new_autofocus/endpoints.py create mode 100644 new_autofocus/routine.py create mode 100644 new_autofocus/routine.py~ diff --git a/.app.py.un~ b/.app.py.un~ new file mode 100644 index 0000000000000000000000000000000000000000..10530277d377b932f37ab68864ff38475d209150 GIT binary patch literal 1480 zcmWH`%$*;a=aT=Ffr)?Lh3!+PZodEh&tkWqT#8}}drx1q^k40FXYw!ej;Fu3FfcHQ zFfcGECYF}ur{yP?7V8zIre_wHq!z^|<&>rt6lLa>Xe1UCXaW^-0TqG(BM>u#Sr8h; z2J^Fc#80w7WaBSD6)-_*5CxQCVh7T)AkKdv02>A3i~<^jKo*avDmXeA8NNyYgPskD zxq(;^Xcs7!WbsD0X(Koeks}-AMqqq0aRO;jJdVa8C=h50vUtQCz;Va~i9-%x@B{N9 zIMafnMj$h{Ait", "/", "\\", '"', "|", "?", "*", ".."] + # if invalid filename, use image.fits + if file is None or file == "" or any(c in file for c in invalid_characters): + all_files = list(sorted(glob(os.path.join(path, "ecam-*.fits")))) + if len(all_files) == 0: + seq = 1 + else: + match = re.search(r"ecam\-([0-9]+)", all_files[-1]) + if match: + seq = int(match.group(1)) + 1 + else: + seq = 1 + file = default_image_name.format(seq=seq) + + # ensure extension is .fits + if file[-1] == ".": + file += "fits" + if len(file) < 5 or file[-5:] != ".fits": + file += ".fits" + + # ensure nothing gets overwritten + num = 0 + length = len(file[0:-5]) + while os.path.isfile(f"{DEFAULT_PATH}/{file}"): + file = file[0:length] + f"({num})" + file[-5:] + num += 1 + + return os.path.join(path, file) + + +async def send_to_wheel(command: str): + '''Sends a command to the filter wheel and parses the reply. + + Parameters + ---------- + command + The string to send to the filter wheel server. + + Returns + ------- + res + A tuple of response status as a boolean, and the additional reply + as a string (the reply string will be empty if no additional reply is + provided). + + ''' + + reader, writer = await asyncio.open_connection('72.233.250.84', 9999) + writer.write((command + '\n').encode()) + await writer.drain() + + received = (await reader.readline()).decode() + writer.close() + await writer.wait_closed() + + parts = received.split(',') + + status = parts[0] == 'OK' + + if len(parts) > 1: + reply = parts[1] + else: + reply = '' + + return status, reply + + +def create_app(test_config=None): + # create and configure the app + app = Flask(__name__, instance_relative_config=True) + CORS(app) + + logging.basicConfig(level=logging.DEBUG) + + if test_config is None: + # load the instance config, if it exists, when not testing + app.config.from_pyfile('config.py', silent=True) + else: + # load the test config if passed in + app.config.from_mapping(test_config) + + @app.route('/getStatus') + def getStatus(): + return jsonify(andor.getStatus()) + + @app.route('/') + def index(): + tempData = andor.getStatusTEC()['temperature'] + return render_template('index.html', tempData=tempData) + + @app.route('/initialize') + def route_initialize(): + status = startup() + activateCooling() # make this a part of a separate route later + return status + + @app.route('/shutdown') + def route_shutdown(): + deactivateCooling() # same here + while not DEBUGGING and andor.getStatusTEC()['temperature'] < -10: + print('waiting to warm: ', andor.getStatusTEC()['temperature']) + time.sleep(5) + # We assume the fan should always be on. Testing to turn it off did not work. + status = andor.shutdown() + return {'status': status} + + @app.route('/getTemperature') + def route_getTemperature(): + print(andor.getStatusTEC()) + return jsonify(andor.getStatusTEC()) + + @app.route('/setTemperature', methods=['POST']) + def route_setTemperature(): + if request.method == 'POST': + req = request.get_json(force=True) + + try: + valid_range: dict = andor.getRangeTEC() + req_temperature = int(req['temperature']) + if req_temperature < valid_range['min'] or req_temperature > valid_range['max']: + app.logger.info( + f'Setting temperature to: {req_temperature:.2f} [C] is out of range. Must be between {valid_range["min"]} and {valid_range["max"]}' + ) + return str(-999) # indicates tempreature was out of range + app.logger.info(f'Setting temperature to: {req_temperature:.2f} [C]') + andor.setTargetTEC(req_temperature) + except ValueError: + app.logger.info( + 'Post request received a parameter of invalid type (must be int)' + ) + + return str(req['temperature']) + + @app.route('/testLongExposure') + def route_testLongExposure(): + acquisition((1024, 1024), exposure_time=10) + return str('Finished Acquiring after 10s') + + @app.route('/getFocus') + def route_getFocus(): + return jsonify({'focus': DUMMY_FOCUS_POSITION if DEBUGGING else '0'}) + + @app.route('/setFocus', methods=['POST']) + def route_setFocus(): + global DUMMY_FOCUS_POSITION + if request.method == 'POST': + req = request.get_json(force=True) + if DEBUGGING: + try: + print(req['focus']) + DUMMY_FOCUS_POSITION = int(req['focus']) + return jsonify({'focus': DUMMY_FOCUS_POSITION}) + except (TypeError, ValueError): + return jsonify({'error': 'Invalid focus value. Must be a number.'}) + return jsonify({'message': 'Done!'}) + return jsonify({'error': 'Invalid request method.'}) + + @app.route("/capture", methods=["POST"]) + async def route_capture(): + ''' + Attempts to take a picture with the camera. Uses the 'POST' method + to take in form requests from the front end. + + Returns the url for the fits file generated, which is then used for + JS9's display. + filename, url, message, status + status: 0 - success, 1 - aborted, 2 - failed + ''' + + global ABORT_FLAG + + ABORT_FLAG = False + + if request.method == 'POST': + req = request.get_json(force=True) + req = json.loads(req) + + # validate parameters + if 'exptime' not in req or 3600 < float(req['exptime']) <= 0: + return {'message': 'Invalid or missing exposure time.', 'status': 2} + if 'exptype' not in req or req['exptype'] not in ['Single', 'Real Time', 'Series']: + return {'message': 'Invalid or missing exposure type', 'status': 2} + if 'imgtype' not in req or req['imgtype'] not in ['Bias', 'Dark', 'Flat', 'Object']: + return {'message': 'Invalid or missing image type.', 'status': 2} + if 'filtype' not in req or req['filtype'] not in FILTER_DICT: + return {'message': 'Invalid or missing filter type.', 'status': 2} + if 'comment' not in req: + return {'message': 'Missing comment.', 'status': 2} + + + dim = andor.getDetector()['dimensions'] + + # check if acquisition is already in progress + status = andor.getStatus() + if status['status'] == 20072: + return {'message': 'Acquisition already in progress.', 'status': 2} + + # handle img type + if req['imgtype'] == 'Bias' or req['imgtype'] == 'Dark': + # Keep shutter closed during biases and darks + andor.setShutter(1, 2, 50, 50) + andor.setImage(1, 1, 1, dim[0], 1, dim[1]) + else: + andor.setShutter(1, 0, 50, 50) + andor.setImage(1, 1, 1, dim[0], 1, dim[1]) + + # handle exposure type + # refer to pg 41 - 45 of sdk for acquisition mode info + exptype = req['exptype'] + if exptype == 'Single': + andor.setAcquisitionMode(1) + andor.setExposureTime(float(req['exptime'])) + + elif exptype == 'Real Time': + # this uses 'run till abort' mode - how do we abort it? + andor.setAcquisitionMode(1) + andor.setExposureTime(1) + # andor.setKineticCycleTime(0) + req['exptime'] = 1 + + elif exptype == 'Series': + andor.setAcquisitionMode(3) + andor.setNumberKinetics(int(req['expnum'])) + andor.setExposureTime(float(req['exptime'])) + + file_name = ( + f'{DEFAULT_PATH}/temp.fits' + if exptype == 'Real Time' + else getFilePath(None) + ) + + date_obs = Time.now() + + andor.startAcquisition() + status = andor.getStatus() + # todo: review parallelism, threading behavior is what we want? + # while status == 20072: + # status = andor.getStatus() + # app.logger.info('Acquisition in progress') + + start_time = datetime.now() + + while (datetime.now() - start_time).total_seconds() < float(req["exptime"]): + if ABORT_FLAG: + andor.abortAcquisition() + return {'message': str('Capture aborted'), 'status': 1} + await asyncio.sleep(0.1) + + # An additional delay because the camera may not have totally finished + # acquiring after exptime. + await asyncio.sleep(0.5) + + img = andor.getAcquiredData( + dim + ) # TODO: throws an error here! gotta wait for acquisition + + comment = req['comment'] + + focus_match = re.match(r'^focus\s*[:=]\s*(-?[0-9\.]+)$', comment) + if focus_match is not None: + focus = float(focus_match.group(1)) + else: + focus = '' + + if img['status'] == 20002: + # use astropy here to write a fits file + andor.setShutter(1, 0, 50, 50) # closes shutter + # home_filter() # uncomment if using filter wheel + hdu = fits.PrimaryHDU(img['data'].astype(numpy.uint16)) + hdu.header['DATE-OBS'] = date_obs.isot + hdu.header['COMMENT'] = comment + hdu.header['INSTRUME'] = 'iKon-M 934 CCD DU934P-BEX2-DD' + hdu.header['XBINNING'] = '1' + hdu.header['YBINNING'] = '1' + hdu.header['XPIXSZ'] = '13' + hdu.header['YPIXSZ'] = '13' + hdu.header['FOCALLEN'] = '5766' + + hdu.header['EXPTIME'] = ( + float(req['exptime']), + 'Exposure Time (Seconds)', + ) + hdu.header['EXP_TYPE'] = ( + str(req['exptype']), + 'Exposure Type (Single, Real Time, or Series)', + ) + hdu.header['IMAGETYP'] = ( + str(req['imgtype']), + 'Image Type (Bias, Flat, Dark, or Object)', + ) + hdu.header['FILTER'] = (str(req['filtype']), 'Filter (Ha, B, V, g, r)') + hdu.header['CCD-TEMP'] = ( + str(f'{andor.getStatusTEC()["temperature"]:.3f}'), + 'CCD Temperature during Exposure', + ) + hdu.header['FOCUS'] = ( + focus, + 'Relative focus position [microns]' + ) + try: + hdu.writeto(file_name, overwrite=True) + except: + print('Failed to write') + + return { + 'filename': os.path.basename(file_name), + 'url': file_name, + 'message': 'Capture Successful', + 'status': 0 + } + + else: + andor.setShutter(1, 0, 50, 50) + # home_filter() # uncomment if using filter wheel + return {'message': str('Capture Unsuccessful'), 'status': 2} + + @app.route('/abort') + async def route_abort_capture(): + '''Abort exposure.''' + + global ABORT_FLAG + + ABORT_FLAG = True + + return {'message': 'Aborting exposure'} + + @app.route('/getFilterWheel') + async def route_get_filter_wheel(): + '''Returns the position of the filter wheel.''' + if DEBUGGING: + return jsonify({'success': True, 'filter': FILTER_DICT_REVERSE[DUMMY_FILTER_POSITION], 'error': ''}) + + status, reply = await send_to_wheel('get') + filter_name = None + error = '' + + if status: + success = True + filter_pos = int(reply) + filter_name = FILTER_DICT_REVERSE[filter_pos] + else: + success = False + error = reply + + return jsonify({'success': success, 'filter': filter_name, 'error': error}) + + @app.route('/setFilterWheel', methods=['POST']) + async def route_set_filter_wheel(): + '''Moves the filter wheel to a given position by filter name.''' + + global DUMMY_FILTER_POSITION + + payload = dict( + message='', + success=False, + error='', + ) + + if request.method == 'POST': + req = request.get_json(force=True) + else: + payload['error'] = 'Invalid request method.' + return jsonify(payload) + + if 'filter' not in req: + payload['error'] = 'Filter not found in request.' + return jsonify(payload) + + filter = req['filter'] + if filter not in FILTER_DICT: + payload['error'] = f'Unknown filter {filter}.' + return jsonify(payload) + + filter_num = FILTER_DICT[filter] + + if DEBUGGING: + await asyncio.sleep(2) + DUMMY_FILTER_POSITION = filter_num + payload['success'] = True + return jsonify(payload) + + status, reply = await send_to_wheel(f'move {filter_num}') + + payload['success'] = status + if status: + payload['message'] = f'Filter wheel moved to filter {filter}.' + else: + payload['error'] = reply + + return jsonify(payload) + + @app.route('/homeFilterWheel') + async def route_home_filter_wheel(): + '''Homes the filter wheel.''' + + global DUMMY_FILTER_POSITION + + if DEBUGGING: + await asyncio.sleep(2) + DUMMY_FILTER_POSITION = 0 + return dict(message='', success=True, error='') + + payload = dict( + message='', + success=False, + error='', + ) + + status, reply = await send_to_wheel('home') + payload['success'] = status + if status: + payload['message'] = 'Filter wheel has been homed.' + else: + payload['error'] = reply + print(payload) + return jsonify(payload) + + @app.route('/getWeatherData') + def route_get_weather_data(): + devices = api.get_devices() + device = devices[0] + time.sleep(1) + data = device.last_data + return data + + return app + + +def OnExitApp(): + andor.shutdown() + + +atexit.register(OnExitApp) + +app = create_app() + +# Framing does not work on windows (due to astrometry) +if sys.platform != 'win32': + import framing + framing.register_blueprint(app) + +import focus +focus.register_blueprint(app) + +import new_autofocus +autofocus.register_blueprint(app) + +if __name__ == '__main__': + # TO RUN IN PRODUCTION, USE: + # The key here is threaded=True which allows the server to handle multiple + # requests at once but withing a single process (?), which allows sharing the + # camera connection. + # app.run(host='127.0.0.1', port=8000, debug=False, threaded=True, processes=1) + + # FOR DEBUGGING, USE: + app.run(host='127.0.0.1', port=3000, debug=True, processes=1, threaded=True) diff --git a/evora/.debug.py.un~ b/evora/.debug.py.un~ new file mode 100644 index 0000000000000000000000000000000000000000..95326e4003b460d8c56c2e5562b73a9bdd2485cb GIT binary patch literal 992 zcmWH`%$*;a=aT=Ff$6kG`9n_~)kc+Vq5lu|FOTgtR$n7lQXzD7mo>u#dGo^z3=E7w zEa>9u6zcBo>F2IstKgQHQ=AHw;DORiKpF%Fj@rcN>fV49*#9si(0s%}Rhz*fq zU;r7y@E-`kA|TEvpg{-#qeB`T9SjU#CD6>r9?7PS;CKT`A_Gtyg2cE%f}`;U3I$pM VV7$pdvk}&K<3Y}xrj5^60RT)RF!TTb literal 0 HcmV?d00001 diff --git a/evora/bin/Activate.ps1 b/evora/bin/Activate.ps1 new file mode 100644 index 0000000..b49d77b --- /dev/null +++ b/evora/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/evora/bin/activate b/evora/bin/activate new file mode 100644 index 0000000..cfcf838 --- /dev/null +++ b/evora/bin/activate @@ -0,0 +1,63 @@ +# This file must be used with "source bin/activate" *from bash* +# you cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # Call hash to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + hash -r 2> /dev/null + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +VIRTUAL_ENV="/Users/andy/Documents/UW/aueg/evora-server/evora" +export VIRTUAL_ENV + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/bin:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1="(evora) ${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT="(evora) " + export VIRTUAL_ENV_PROMPT +fi + +# Call hash to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +hash -r 2> /dev/null diff --git a/evora/bin/activate.csh b/evora/bin/activate.csh new file mode 100644 index 0000000..5ecb6f1 --- /dev/null +++ b/evora/bin/activate.csh @@ -0,0 +1,26 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV "/Users/andy/Documents/UW/aueg/evora-server/evora" + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/bin:$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = "(evora) $prompt" + setenv VIRTUAL_ENV_PROMPT "(evora) " +endif + +alias pydoc python -m pydoc + +rehash diff --git a/evora/bin/activate.fish b/evora/bin/activate.fish new file mode 100644 index 0000000..ff4648b --- /dev/null +++ b/evora/bin/activate.fish @@ -0,0 +1,69 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/); you cannot run it directly. + +function deactivate -d "Exit virtual environment and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + set -e _OLD_FISH_PROMPT_OVERRIDE + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV "/Users/andy/Documents/UW/aueg/evora-server/evora" + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/bin" $PATH + +# Unset PYTHONHOME if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # Save the current fish_prompt function as the function _old_fish_prompt. + functions -c fish_prompt _old_fish_prompt + + # With the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command. + set -l old_status $status + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) "(evora) " (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT "(evora) " +end diff --git a/evora/bin/dotenv b/evora/bin/dotenv new file mode 100755 index 0000000..a713c1b --- /dev/null +++ b/evora/bin/dotenv @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from dotenv.__main__ import cli +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli()) diff --git a/evora/bin/f2py b/evora/bin/f2py new file mode 100755 index 0000000..18981f4 --- /dev/null +++ b/evora/bin/f2py @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from numpy.f2py.f2py2e import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/fits2bitmap b/evora/bin/fits2bitmap new file mode 100755 index 0000000..43f9d75 --- /dev/null +++ b/evora/bin/fits2bitmap @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from astropy.visualization.scripts.fits2bitmap import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/fitscheck b/evora/bin/fitscheck new file mode 100755 index 0000000..e9e8c5f --- /dev/null +++ b/evora/bin/fitscheck @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from astropy.io.fits.scripts.fitscheck import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/fitsdiff b/evora/bin/fitsdiff new file mode 100755 index 0000000..59e6806 --- /dev/null +++ b/evora/bin/fitsdiff @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from astropy.io.fits.scripts.fitsdiff import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/fitsheader b/evora/bin/fitsheader new file mode 100755 index 0000000..350f7e7 --- /dev/null +++ b/evora/bin/fitsheader @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from astropy.io.fits.scripts.fitsheader import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/fitsinfo b/evora/bin/fitsinfo new file mode 100755 index 0000000..2c991ce --- /dev/null +++ b/evora/bin/fitsinfo @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from astropy.io.fits.scripts.fitsinfo import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/flask b/evora/bin/flask new file mode 100755 index 0000000..c6ea6db --- /dev/null +++ b/evora/bin/flask @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from flask.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/fonttools b/evora/bin/fonttools new file mode 100755 index 0000000..8ec0234 --- /dev/null +++ b/evora/bin/fonttools @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from fontTools.__main__ import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/gunicorn b/evora/bin/gunicorn new file mode 100755 index 0000000..ac83fbd --- /dev/null +++ b/evora/bin/gunicorn @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from gunicorn.app.wsgiapp import run +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(run()) diff --git a/evora/bin/gunicornc b/evora/bin/gunicornc new file mode 100755 index 0000000..a88f5d6 --- /dev/null +++ b/evora/bin/gunicornc @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from gunicorn.ctl.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/normalizer b/evora/bin/normalizer new file mode 100755 index 0000000..0b84a3c --- /dev/null +++ b/evora/bin/normalizer @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from charset_normalizer.cli import cli_detect +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli_detect()) diff --git a/evora/bin/numpy-config b/evora/bin/numpy-config new file mode 100755 index 0000000..b5a641a --- /dev/null +++ b/evora/bin/numpy-config @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from numpy._configtool import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/pip b/evora/bin/pip new file mode 100755 index 0000000..3460890 --- /dev/null +++ b/evora/bin/pip @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/pip3 b/evora/bin/pip3 new file mode 100755 index 0000000..3460890 --- /dev/null +++ b/evora/bin/pip3 @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/pip3.11 b/evora/bin/pip3.11 new file mode 100755 index 0000000..3460890 --- /dev/null +++ b/evora/bin/pip3.11 @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/pyftmerge b/evora/bin/pyftmerge new file mode 100755 index 0000000..4094ad2 --- /dev/null +++ b/evora/bin/pyftmerge @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from fontTools.merge import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/pyftsubset b/evora/bin/pyftsubset new file mode 100755 index 0000000..cf7ac04 --- /dev/null +++ b/evora/bin/pyftsubset @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from fontTools.subset import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/python b/evora/bin/python new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/evora/bin/python @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/evora/bin/python3 b/evora/bin/python3 new file mode 120000 index 0000000..51123f3 --- /dev/null +++ b/evora/bin/python3 @@ -0,0 +1 @@ +/Users/andy/.pyenv/versions/3.11.10/bin/python3 \ No newline at end of file diff --git a/evora/bin/python3.11 b/evora/bin/python3.11 new file mode 120000 index 0000000..b8a0adb --- /dev/null +++ b/evora/bin/python3.11 @@ -0,0 +1 @@ +python3 \ No newline at end of file diff --git a/evora/bin/samp_hub b/evora/bin/samp_hub new file mode 100755 index 0000000..c7c06ec --- /dev/null +++ b/evora/bin/samp_hub @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from astropy.samp.hub_script import hub_script +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(hub_script()) diff --git a/evora/bin/showtable b/evora/bin/showtable new file mode 100755 index 0000000..d8dd6a0 --- /dev/null +++ b/evora/bin/showtable @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from astropy.table.scripts.showtable import main_deprecated +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main_deprecated()) diff --git a/evora/bin/showtable-astropy b/evora/bin/showtable-astropy new file mode 100755 index 0000000..0b72121 --- /dev/null +++ b/evora/bin/showtable-astropy @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from astropy.table.scripts.showtable import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/ttx b/evora/bin/ttx new file mode 100755 index 0000000..ea8bbbb --- /dev/null +++ b/evora/bin/ttx @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from fontTools.ttx import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/volint b/evora/bin/volint new file mode 100755 index 0000000..fd807f3 --- /dev/null +++ b/evora/bin/volint @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from astropy.io.votable.volint import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/bin/wcslint b/evora/bin/wcslint new file mode 100755 index 0000000..37b9561 --- /dev/null +++ b/evora/bin/wcslint @@ -0,0 +1,8 @@ +#!/Users/andy/Documents/UW/aueg/evora-server/evora/bin/python3 +# -*- coding: utf-8 -*- +import re +import sys +from astropy.wcs.wcslint import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/evora/debug.py b/evora/debug.py index b388684..547f997 100644 --- a/evora/debug.py +++ b/evora/debug.py @@ -1 +1 @@ -DEBUGGING = False +DEBUGGING = True diff --git a/evora/debug.py~ b/evora/debug.py~ new file mode 100644 index 0000000..b388684 --- /dev/null +++ b/evora/debug.py~ @@ -0,0 +1 @@ +DEBUGGING = False diff --git a/evora/pyvenv.cfg b/evora/pyvenv.cfg new file mode 100644 index 0000000..415a240 --- /dev/null +++ b/evora/pyvenv.cfg @@ -0,0 +1,5 @@ +home = /Users/andy/.pyenv/versions/3.11.10/bin +include-system-site-packages = false +version = 3.11.10 +executable = /Users/andy/.pyenv/versions/3.11.10/bin/python3.11 +command = /Users/andy/.pyenv/versions/3.11.10/bin/python3 -m venv /Users/andy/Documents/UW/aueg/evora-server/evora diff --git a/evora/share/man/man1/ttx.1 b/evora/share/man/man1/ttx.1 new file mode 100644 index 0000000..bba23b5 --- /dev/null +++ b/evora/share/man/man1/ttx.1 @@ -0,0 +1,225 @@ +.Dd May 18, 2004 +.\" ttx is not specific to any OS, but contrary to what groff_mdoc(7) +.\" seems to imply, entirely omitting the .Os macro causes 'BSD' to +.\" be used, so I give a zero-width space as its argument. +.Os \& +.\" The "FontTools Manual" argument apparently has no effect in +.\" groff 1.18.1. I think it is a bug in the -mdoc groff package. +.Dt TTX 1 "FontTools Manual" +.Sh NAME +.Nm ttx +.Nd tool for manipulating TrueType and OpenType fonts +.Sh SYNOPSIS +.Nm +.Bk +.Op Ar option ... +.Ek +.Bk +.Ar file ... +.Ek +.Sh DESCRIPTION +.Nm +is a tool for manipulating TrueType and OpenType fonts. It can convert +TrueType and OpenType fonts to and from an +.Tn XML Ns -based format called +.Tn TTX . +.Tn TTX +files have a +.Ql .ttx +extension. +.Pp +For each +.Ar file +argument it is given, +.Nm +detects whether it is a +.Ql .ttf , +.Ql .otf +or +.Ql .ttx +file and acts accordingly: if it is a +.Ql .ttf +or +.Ql .otf +file, it generates a +.Ql .ttx +file; if it is a +.Ql .ttx +file, it generates a +.Ql .ttf +or +.Ql .otf +file. +.Pp +By default, every output file is created in the same directory as the +corresponding input file and with the same name except for the +extension, which is substituted appropriately. +.Nm +never overwrites existing files; if necessary, it appends a suffix to +the output file name before the extension, as in +.Pa Arial#1.ttf . +.Ss "General options" +.Bl -tag -width ".Fl t Ar table" +.It Fl h +Display usage information. +.It Fl d Ar dir +Write the output files to directory +.Ar dir +instead of writing every output file to the same directory as the +corresponding input file. +.It Fl o Ar file +Write the output to +.Ar file +instead of writing it to the same directory as the +corresponding input file. +.It Fl v +Be verbose. Write more messages to the standard output describing what +is being done. +.It Fl a +Allow virtual glyphs ID's on compile or decompile. +.El +.Ss "Dump options" +The following options control the process of dumping font files +(TrueType or OpenType) to +.Tn TTX +files. +.Bl -tag -width ".Fl t Ar table" +.It Fl l +List table information. Instead of dumping the font to a +.Tn TTX +file, display minimal information about each table. +.It Fl t Ar table +Dump table +.Ar table . +This option may be given multiple times to dump several tables at +once. When not specified, all tables are dumped. +.It Fl x Ar table +Exclude table +.Ar table +from the list of tables to dump. This option may be given multiple +times to exclude several tables from the dump. The +.Fl t +and +.Fl x +options are mutually exclusive. +.It Fl s +Split tables. Dump each table to a separate +.Tn TTX +file and write (under the name that would have been used for the output +file if the +.Fl s +option had not been given) one small +.Tn TTX +file containing references to the individual table dump files. This +file can be used as input to +.Nm +as long as the referenced files can be found in the same directory. +.It Fl i +.\" XXX: I suppose OpenType programs (exist and) are also affected. +Don't disassemble TrueType instructions. When this option is specified, +all TrueType programs (glyph programs, the font program and the +pre-program) are written to the +.Tn TTX +file as hexadecimal data instead of +assembly. This saves some time and results in smaller +.Tn TTX +files. +.It Fl y Ar n +When decompiling a TrueType Collection (TTC) file, +decompile font number +.Ar n , +starting from 0. +.El +.Ss "Compilation options" +The following options control the process of compiling +.Tn TTX +files into font files (TrueType or OpenType): +.Bl -tag -width ".Fl t Ar table" +.It Fl m Ar fontfile +Merge the input +.Tn TTX +file +.Ar file +with +.Ar fontfile . +No more than one +.Ar file +argument can be specified when this option is used. +.It Fl b +Don't recalculate glyph bounding boxes. Use the values in the +.Tn TTX +file as is. +.El +.Sh "THE TTX FILE FORMAT" +You can find some information about the +.Tn TTX +file format in +.Pa documentation.html . +In particular, you will find in that file the list of tables understood by +.Nm +and the relations between TrueType GlyphIDs and the glyph names used in +.Tn TTX +files. +.Sh EXAMPLES +In the following examples, all files are read from and written to the +current directory. Additionally, the name given for the output file +assumes in every case that it did not exist before +.Nm +was invoked. +.Pp +Dump the TrueType font contained in +.Pa FreeSans.ttf +to +.Pa FreeSans.ttx : +.Pp +.Dl ttx FreeSans.ttf +.Pp +Compile +.Pa MyFont.ttx +into a TrueType or OpenType font file: +.Pp +.Dl ttx MyFont.ttx +.Pp +List the tables in +.Pa FreeSans.ttf +along with some information: +.Pp +.Dl ttx -l FreeSans.ttf +.Pp +Dump the +.Sq cmap +table from +.Pa FreeSans.ttf +to +.Pa FreeSans.ttx : +.Pp +.Dl ttx -t cmap FreeSans.ttf +.Sh NOTES +On MS\-Windows and MacOS, +.Nm +is available as a graphical application to which files can be dropped. +.Sh SEE ALSO +.Pa documentation.html +.Pp +.Xr fontforge 1 , +.Xr ftinfo 1 , +.Xr gfontview 1 , +.Xr xmbdfed 1 , +.Xr Font::TTF 3pm +.Sh AUTHORS +.Nm +was written by +.An -nosplit +.An "Just van Rossum" Aq just@letterror.com . +.Pp +This manual page was written by +.An "Florent Rougon" Aq f.rougon@free.fr +for the Debian GNU/Linux system based on the existing FontTools +documentation. It may be freely used, modified and distributed without +restrictions. +.\" For Emacs: +.\" Local Variables: +.\" fill-column: 72 +.\" sentence-end: "[.?!][]\"')}]*\\($\\| $\\| \\| \\)[ \n]*" +.\" sentence-end-double-space: t +.\" End: \ No newline at end of file diff --git a/new_autofocus/.__init__.py.un~ b/new_autofocus/.__init__.py.un~ new file mode 100644 index 0000000000000000000000000000000000000000..d5a4c856fd87dd6f10dd5d6c4a3e1c142f3f0d53 GIT binary patch literal 523 zcmWH`%$*;a=aT=FfvNC>rsJL%?oaXm`FE&Q@%NUmWV%={qi!L{ce7ha>!=+A0|P4% zBLhYt2ZTWY#s>4VctqB-Kt$p%K;%(Km}0Qte;{CBMv))oP&EXA(XkF19bYBTOa{l7 IY2))%0MU6H{Qv*} literal 0 HcmV?d00001 diff --git a/new_autofocus/.endpoints.py.un~ b/new_autofocus/.endpoints.py.un~ new file mode 100644 index 0000000000000000000000000000000000000000..07af582b34090bc806f3511f3f387fa52e8d52ab GIT binary patch literal 523 zcmWH`%$*;a=aT=FfvHqF#QTrQx>q}F@0a;Z)Uz^Ov9{ZP#p>OfdEcl1DTs14kMYBGTA_#uoxHIN+ht z1d0||SqZWo6lHuM1_+Es6C@mHM*^d16>{F<1zH6Qeo!=trxoSrDx{X>7bPmdvRPhA zei1bGkTV#_k)W7_X&;R}P>9eJ0Ap_*IQEz!nG8Ml6cMqfmy()PnhuLY7gwiHcXv-e Jchkn_s{otBTF?Lh literal 0 HcmV?d00001 diff --git a/new_autofocus/__init__.py b/new_autofocus/__init__.py new file mode 100644 index 0000000..cc9d6cc --- /dev/null +++ b/new_autofocus/__init__.py @@ -0,0 +1,5 @@ +from flask import Blueprint +from .endpoints import blueprint + +def register_blueprint(app): + app.register_blueprint(blueprint) diff --git a/new_autofocus/endpoints.py b/new_autofocus/endpoints.py new file mode 100644 index 0000000..9baa311 --- /dev/null +++ b/new_autofocus/endpoints.py @@ -0,0 +1,59 @@ +from evora.debug import DEBUGGING + +if DEBUGGING: + from evora.dummy import Dummy as andor +else: + from evora import andor + +from flask import Blueprint, request, jsonify +import logging +from .routine import measure_hfd, move_focuser, calculate_best_focus, save_temp_fits, generate_mock_debug_image +from andor_routines import acquisition + +blueprint = Blueprint('autofocus', __name__) + +@blueprint.route('/autofocus/step', methods=['POST']) +async def autofocus_step(): + """Takes 1 picture, measures HFD, saves temp file, and moves motor 1 step.""" + req = request.get_json(force=True) if request.is_json else {} + exposure = float(req.get('exposure', 1.0)) + step_size = int(req.get('step_size', 500)) + is_last_step = req.get('is_last_step', False) + + # take exposure + if DEBUGGING: + img_data = generate_mock_debug_image() + else: + dim = andor.getDetector()['dimensions'] + result = acquisition(dim, exposure_time=exposure) + if result.get("status") != 20002: + return jsonify({"status": "error", "message": "Camera acquisition failed."}), 500 + img_data = result["data"] + + # measures hfd + hfd = measure_hfd(img_data) + + # save temp file + file_url = save_temp_fits(img_data) + + # moves focuser + if not is_last_step: + await move_focuser(-step_size) + + return jsonify({ + "status": "success", + "hfd": hfd, + "image_url": file_url + }) + +@blueprint.route('/autofocus/analyze', methods=['POST']) +async def autofocus_analyze(): + """Takes the array of data from the frontend and finishes the job.""" + req = request.get_json(force=True) if request.is_json else {} + positions = req.get('positions', []) + hfds = req.get('hfds', []) + step_size = req.get('step_size', 10) + total_steps = req.get('total_steps', 20) + + result = await calculate_best_focus(positions, hfds, step_size, total_steps) + return jsonify(result) \ No newline at end of file diff --git a/new_autofocus/routine.py b/new_autofocus/routine.py new file mode 100644 index 0000000..89f0278 --- /dev/null +++ b/new_autofocus/routine.py @@ -0,0 +1,93 @@ +import asyncio +import aiohttp +import numpy as np +import sep_pjw as sep +import logging +from astropy.io import fits +import os +import random +from evora.debug import DEBUGGING + +TINYFOCUS_URL = "http://127.0.0.1:5000" + +# Mirror the app.py pathing logic +if DEBUGGING: + DEFAULT_PATH = './data/ecam' +else: + DEFAULT_PATH = '/data/ecam' + +async def move_focuser(steps: int): + """Sends HTTP command to tinyfocus-server.""" + if DEBUGGING: + logging.info(f"[DEBUG] Mock moving focuser by {steps} steps") + return {"code": 200} + + async with aiohttp.ClientSession() as session: + async with session.get(f"{TINYFOCUS_URL}/api/move?steps={steps}") as resp: + return await resp.json() + +def generate_mock_debug_image(): + """Generates a 2D numpy array with noise and a random circle for debug mode.""" + img_data = np.random.randint(100, 200, (1024, 1024), dtype=np.uint16) + radius = random.randint(5, 40) + cy, cx = 512, 512 + y, x = np.ogrid[:1024, :1024] + mask = (x - cx)**2 + (y - cy)**2 <= radius**2 + img_data[mask] = 50000 # bright star + return img_data + +def measure_hfd(image_data): + try: + data = image_data.astype(np.float64) + bkg = sep.Background(data) + data_sub = data - bkg + objects = sep.extract(data_sub, 3.0, err=bkg.globalrms) + + if len(objects) == 0: return 99.0 + + flux, fluxerr, flag = sep.sum_circle(data_sub, objects['x'], objects['y'], 5.0, subpix=1, bkgann=(10.0, 15.0)) + top_indices = np.argsort(flux)[-15:] + + hfr_array, _ = sep.flux_radius(data_sub, objects['x'][top_indices], objects['y'][top_indices], 6.0 * objects['a'][top_indices], 0.5, normflux=flux[top_indices], subpix=5) + + valid_hfr = [r for r in hfr_array if r > 0 and not np.isnan(r)] + return np.mean(valid_hfr) * 2.0 if valid_hfr else 99.0 + except Exception as e: + logging.error(f"HFD error: {e}") + return 99.0 + +def save_temp_fits(img_data): + """Saves the image to the disk so JS9 can load it via the file server.""" + os.makedirs(DEFAULT_PATH, exist_ok=True) + + file_path = f"{DEFAULT_PATH}/temp_focus.fits" + url_path = "/data/ecam/temp_focus.fits" # Always the absolute proxy path for the frontend + + hdu = fits.PrimaryHDU(img_data.astype(np.uint16)) + hdu.writeto(file_path, overwrite=True) + return url_path + +async def calculate_best_focus(positions, hfds, step_size, total_steps): + """Analyzes the array of data collected by the frontend and moves to the vertex.""" + valid_data = [(p, h) for p, h in zip(positions, hfds) if h < 90.0] + + if len(valid_data) < 3: + return {"status": "error", "message": "Not enough valid stars detected."} + + x = np.array([d[0] for d in valid_data]) + y = np.array([d[1] for d in valid_data]) + + a, b, c = np.polyfit(x, y, 2) + + if a > 0: + vertex_x = -b / (2 * a) + best_position = max(0, min(int(round(vertex_x)), (total_steps - 1) * step_size)) + + current_position = max(positions) + steps_back = current_position - best_position + + await move_focuser(steps_back) + + return {"status": "success", "best_position": best_position} + else: + return {"status": "error", "message": "Curve fit failed."} \ No newline at end of file diff --git a/new_autofocus/routine.py~ b/new_autofocus/routine.py~ new file mode 100644 index 0000000..a691119 --- /dev/null +++ b/new_autofocus/routine.py~ @@ -0,0 +1,118 @@ +from evora.debug import DEBUGGING + +if DEBUGGING: + from evora.dummy import Dummy as andor +else: + from evora import andor + +import asyncio +import aiohttp +import numpy as np +import sep_pjw as sep +import logging +from andor_routines import acquisition +from evora import andor +from evora.debug import DEBUGGING + +# Default tinyfocus-server URL +TINYFOCUS_URL = "http://127.0.0.1:5000" + +async def move_focuser(steps: int): + """Sends an HTTP command to the tinyfocus-server to move the motor.""" + if DEBUGGING: + logging.info(f"[DEBUG] Mock moving focuser by {steps} steps") + return {"code": 200, "status": "mocked"} + + async with aiohttp.ClientSession() as session: + async with session.get(f"{TINYFOCUS_URL}/api/move?steps={steps}") as resp: + if resp.status != 200: + raise RuntimeError(f"Focuser move failed with status {resp.status}") + return await resp.json() + +def measure_hfd(image_data): + """Calculates the mean HFD from the raw Andor image matrix.""" + try: + data = image_data.astype(np.float64) + bkg = sep.Background(data) + data_sub = data - bkg + objects = sep.extract(data_sub, 3.0, err=bkg.globalrms) + + if len(objects) == 0: + return 99.0 + + flux, fluxerr, flag = sep.sum_circle( + data_sub, objects['x'], objects['y'], + 5.0, subpix=1, bkgann=(10.0, 15.0) + ) + + top_indices = np.argsort(flux)[-15:] + hfr_array, _ = sep.flux_radius( + data_sub, objects['x'][top_indices], objects['y'][top_indices], + 6.0 * objects['a'][top_indices], 0.5, normflux=flux[top_indices], subpix=5 + ) + + valid_hfr = [r for r in hfr_array if r > 0 and not np.isnan(r)] + if not valid_hfr: + return 99.0 + + return np.mean(valid_hfr) * 2.0 + except Exception as e: + logging.error(f"HFD Calculation error: {e}") + return 99.0 + +async def run_v_curve(total_steps=20, step_size=10, exposure_time=1.0): + """Automated V-Curve routine.""" + positions = [] + hfds = [] + + dim = (1024, 1024) if DEBUGGING else andor.getDetector()['dimensions'] + + for step_num in range(total_steps): + result = acquisition(dim, exposure_time=exposure_time) + if result.get("status") != 20002: + logging.warning(f"Failed to acquire image at step {step_num}") + continue + + img_data = result["data"] + hfd = measure_hfd(img_data) + + positions.append(step_num * step_size) + hfds.append(hfd) + + if step_num < total_steps - 1: + await move_focuser(-step_size) + await asyncio.sleep(0.5) + + valid_data = [(p, h) for p, h in zip(positions, hfds) if h < 90.0] + + if len(valid_data) < 3: + return {"status": "error", "message": "Not enough valid stars detected."} + + x = np.array([d[0] for d in valid_data]) + y = np.array([d[1] for d in valid_data]) + + coeffs = np.polyfit(x, y, 2) + a, b, c = coeffs + + if a > 0: + vertex_x = -b / (2 * a) + best_position = int(round(vertex_x)) + max_position = (total_steps - 1) * step_size + best_position = max(0, min(best_position, max_position)) + + current_position = max_position + steps_back = current_position - best_position + overshoot = step_size * 4 + total_out = steps_back + overshoot + + await move_focuser(total_out) + await asyncio.sleep(0.5) + await move_focuser(-overshoot) + + return { + "status": "success", + "best_position": best_position, + "curve_data": {"x": x.tolist(), "y": y.tolist(), "coeffs": coeffs.tolist()} + } + else: + return {"status": "error", "message": "Curve fit failed (no clear V-shape detected)."}