-
Notifications
You must be signed in to change notification settings - Fork 2
Save/load config settings #108
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: butter_filter
Are you sure you want to change the base?
Conversation
I'm not sure if it's nice to have JSON and YAMLs that basically contain the same thing but are used differently. Might be more straightforward to have a |
The tests seem to fail for some hash functions. I am not sure how this could be related to the json file saving and loading functionality?! |
i mean the file format is of course not that important. Just saving a file that can always live with the video files in a folder. to basically know what the config was or re-run it if necessary on the same file or other files by another person |
Oh, the quotes and sentences got mixed up lol
No, the file extension isn't so important. I agree that saving config files is nice. But my question is, why don't we copy the config file into the export folder, or make a model export that can be used directly as a config file, instead of manually dumping everything into a file that can't be directly plugged in? So a comment about implementation/interface rather than concept. |
I guess it'll be ideal if we can throw this stuff or some metadata into the video container itself, but it seems tricky with |
sure that makes sense. Would this file still live in the user output folder or when loded replace the actual denoise config yaml in the config folder? for now I never change the actual config file. it stays how it is downloaded. But only temporarily updates from the json file. if -u is used otherwise does what ever is in there |
ideas to change this before merge
|
sorry just seeing this. If i'm getting this right, we want to have some root configuration that might have a bunch of minor variations, e.g. for each folder in some project directory? there are a few planned changes to configs that are relevant here:
The reasons I prefer those rather than something like this is that
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
already commented that i think this should be implemented differently, tho i totally understand the need. just commenting here to show some stuff that could be done differently for learning purposes
metadata = { | ||
"timestamp": datetime.now().isoformat(), | ||
"input_video": str(video_path), | ||
"output_directory": str(output_dir), | ||
"processing_config": { | ||
"noise_patch": { | ||
"enabled": config.noise_patch.enable if config.noise_patch else False, | ||
"method": config.noise_patch.method if config.noise_patch else None, | ||
"gradient_config": ( | ||
{ | ||
"threshold": ( | ||
config.noise_patch.gradient_config.threshold | ||
if config.noise_patch | ||
else None | ||
), | ||
} | ||
if config.noise_patch and config.noise_patch.gradient_config | ||
else None | ||
), | ||
"black_area_config": ( | ||
{ | ||
"consecutive_threshold": ( | ||
config.noise_patch.black_area_config.consecutive_threshold | ||
if config.noise_patch | ||
else None | ||
), | ||
"value_threshold": ( | ||
config.noise_patch.black_area_config.value_threshold | ||
if config.noise_patch | ||
else None | ||
), | ||
} | ||
if config.noise_patch and config.noise_patch.black_area_config | ||
else None | ||
), | ||
}, | ||
"frequency_masking": { | ||
"enabled": config.frequency_masking.enable if config.frequency_masking else False, | ||
"cast_float32": ( | ||
config.frequency_masking.cast_float32 if config.frequency_masking else False | ||
), | ||
"cutoff_radius": ( | ||
config.frequency_masking.spatial_LPF_cutoff_radius | ||
if config.frequency_masking | ||
else None | ||
), | ||
"vertical_BEF": ( | ||
config.frequency_masking.vertical_BEF_cutoff | ||
if config.frequency_masking | ||
else None | ||
), | ||
"horizontal_BEF": ( | ||
config.frequency_masking.horizontal_BEF_cutoff | ||
if config.frequency_masking | ||
else None | ||
), | ||
}, | ||
"butterworth": { | ||
"enabled": config.butter_filter.enable, | ||
"order": config.butter_filter.order, | ||
"cutoff_frequency": config.butter_filter.cutoff_frequency, | ||
"sampling_rate": config.butter_filter.sampling_rate, | ||
}, | ||
}, | ||
"output_files": { | ||
"noise_patch": f"output_{pathstem}_patch.avi", | ||
"freq_mask": f"output_{pathstem}_freq_mask.avi", | ||
"butter_filter": f"output_{pathstem}_butter_filter.avi", | ||
"butter_plot": f"{pathstem}_butter_filter_intensity_plot.png", | ||
}, | ||
} | ||
|
||
# Save as JSON (overwrite any existing file) | ||
log_file = output_dir / "processing_log.json" | ||
logs = {"processing_runs": [metadata]} # Just create new log with single run | ||
|
||
with open(log_file, "w") as f: # "w" mode overwrites existing file | ||
json.dump(logs, f, indent=2) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should do this for a few reasons, not to belabor the point but we should not need to log what our config was after the fact, we should already know that because we already supplied it when we invoked it! it might be nice to offer, but i would really want to just make a structured model for outputs and have this be a part of that. ad-hoc layouts and formats are not gr8 in general.
but anyway, you'd want this to be like:
metadata = { | |
"timestamp": datetime.now().isoformat(), | |
"input_video": str(video_path), | |
"output_directory": str(output_dir), | |
"processing_config": { | |
"noise_patch": { | |
"enabled": config.noise_patch.enable if config.noise_patch else False, | |
"method": config.noise_patch.method if config.noise_patch else None, | |
"gradient_config": ( | |
{ | |
"threshold": ( | |
config.noise_patch.gradient_config.threshold | |
if config.noise_patch | |
else None | |
), | |
} | |
if config.noise_patch and config.noise_patch.gradient_config | |
else None | |
), | |
"black_area_config": ( | |
{ | |
"consecutive_threshold": ( | |
config.noise_patch.black_area_config.consecutive_threshold | |
if config.noise_patch | |
else None | |
), | |
"value_threshold": ( | |
config.noise_patch.black_area_config.value_threshold | |
if config.noise_patch | |
else None | |
), | |
} | |
if config.noise_patch and config.noise_patch.black_area_config | |
else None | |
), | |
}, | |
"frequency_masking": { | |
"enabled": config.frequency_masking.enable if config.frequency_masking else False, | |
"cast_float32": ( | |
config.frequency_masking.cast_float32 if config.frequency_masking else False | |
), | |
"cutoff_radius": ( | |
config.frequency_masking.spatial_LPF_cutoff_radius | |
if config.frequency_masking | |
else None | |
), | |
"vertical_BEF": ( | |
config.frequency_masking.vertical_BEF_cutoff | |
if config.frequency_masking | |
else None | |
), | |
"horizontal_BEF": ( | |
config.frequency_masking.horizontal_BEF_cutoff | |
if config.frequency_masking | |
else None | |
), | |
}, | |
"butterworth": { | |
"enabled": config.butter_filter.enable, | |
"order": config.butter_filter.order, | |
"cutoff_frequency": config.butter_filter.cutoff_frequency, | |
"sampling_rate": config.butter_filter.sampling_rate, | |
}, | |
}, | |
"output_files": { | |
"noise_patch": f"output_{pathstem}_patch.avi", | |
"freq_mask": f"output_{pathstem}_freq_mask.avi", | |
"butter_filter": f"output_{pathstem}_butter_filter.avi", | |
"butter_plot": f"{pathstem}_butter_filter_intensity_plot.png", | |
}, | |
} | |
# Save as JSON (overwrite any existing file) | |
log_file = output_dir / "processing_log.json" | |
logs = {"processing_runs": [metadata]} # Just create new log with single run | |
with open(log_file, "w") as f: # "w" mode overwrites existing file | |
json.dump(logs, f, indent=2) | |
logs = { | |
"timestamp": datetime.now().isoformat(), | |
"input_video": str(video_path), | |
"output_directory": str(output_dir), | |
"config": config.model_dump() | |
} | |
with open(log_file, "w") as f: # "w" mode overwrites existing file | |
json.dump(logs, f, indent=2) |
no reason to change the keys like this when we're saving the config, might as well just dump the whole thing which has 100% of the information that the config has by definition, and is in a format that can be roundtripped if needed. doing it like this would mean that every time we changed the model, we would need to remember to update this.
@@ -0,0 +1 @@ | |||
"""Utility functions for miniscope-io.""" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i have blanket objects to the existence of utils
subpackages, as they are, as a rule, junk drawer subpackages that make maintenance unnecessarily hard. for an advanced case of how this plays out, see linkml/linkml#2371
it is true that sometimes things don't have an obvious place, so a single, short utils.py
module is fine, but once it starts ballooning...
with open(json_path) as f: | ||
log_data = json.load(f) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ideally all i/o operations are separated from processing actions (so, e.g., we wouldn't need to write to a temporary tmp.json
file and then pass the path to this function if we already had a dict).
# Update noise patch config | ||
config.noise_patch.enable = proc_config["noise_patch"]["enabled"] | ||
if config.noise_patch.enable: | ||
config.noise_patch.method = proc_config["noise_patch"]["method"] | ||
if proc_config["noise_patch"]["gradient_config"]: | ||
config.noise_patch.gradient_config.threshold = proc_config["noise_patch"][ | ||
"gradient_config" | ||
]["threshold"] | ||
if proc_config["noise_patch"]["black_area_config"]: | ||
config.noise_patch.black_area_config.consecutive_threshold = proc_config["noise_patch"][ | ||
"black_area_config" | ||
]["consecutive_threshold"] | ||
config.noise_patch.black_area_config.value_threshold = proc_config["noise_patch"][ | ||
"black_area_config" | ||
]["value_threshold"] | ||
|
||
# Update frequency masking config | ||
config.frequency_masking.enable = proc_config["frequency_masking"]["enabled"] | ||
if config.frequency_masking.enable: | ||
config.frequency_masking.cast_float32 = proc_config["frequency_masking"]["cast_float32"] | ||
config.frequency_masking.spatial_LPF_cutoff_radius = proc_config["frequency_masking"][ | ||
"cutoff_radius" | ||
] | ||
config.frequency_masking.vertical_BEF_cutoff = proc_config["frequency_masking"][ | ||
"vertical_BEF" | ||
] | ||
config.frequency_masking.horizontal_BEF_cutoff = proc_config["frequency_masking"][ | ||
"horizontal_BEF" | ||
] | ||
|
||
# Update butterworth config | ||
config.butter_filter.enable = proc_config["butterworth"]["enabled"] | ||
if config.butter_filter.enable: | ||
config.butter_filter.order = proc_config["butterworth"]["order"] | ||
config.butter_filter.cutoff_frequency = proc_config["butterworth"]["cutoff_frequency"] | ||
config.butter_filter.sampling_rate = proc_config["butterworth"]["sampling_rate"] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same problem as above, lack of generality, need for maintenance if model changes.
# Update noise patch config | |
config.noise_patch.enable = proc_config["noise_patch"]["enabled"] | |
if config.noise_patch.enable: | |
config.noise_patch.method = proc_config["noise_patch"]["method"] | |
if proc_config["noise_patch"]["gradient_config"]: | |
config.noise_patch.gradient_config.threshold = proc_config["noise_patch"][ | |
"gradient_config" | |
]["threshold"] | |
if proc_config["noise_patch"]["black_area_config"]: | |
config.noise_patch.black_area_config.consecutive_threshold = proc_config["noise_patch"][ | |
"black_area_config" | |
]["consecutive_threshold"] | |
config.noise_patch.black_area_config.value_threshold = proc_config["noise_patch"][ | |
"black_area_config" | |
]["value_threshold"] | |
# Update frequency masking config | |
config.frequency_masking.enable = proc_config["frequency_masking"]["enabled"] | |
if config.frequency_masking.enable: | |
config.frequency_masking.cast_float32 = proc_config["frequency_masking"]["cast_float32"] | |
config.frequency_masking.spatial_LPF_cutoff_radius = proc_config["frequency_masking"][ | |
"cutoff_radius" | |
] | |
config.frequency_masking.vertical_BEF_cutoff = proc_config["frequency_masking"][ | |
"vertical_BEF" | |
] | |
config.frequency_masking.horizontal_BEF_cutoff = proc_config["frequency_masking"][ | |
"horizontal_BEF" | |
] | |
# Update butterworth config | |
config.butter_filter.enable = proc_config["butterworth"]["enabled"] | |
if config.butter_filter.enable: | |
config.butter_filter.order = proc_config["butterworth"]["order"] | |
config.butter_filter.cutoff_frequency = proc_config["butterworth"]["cutoff_frequency"] | |
config.butter_filter.sampling_rate = proc_config["butterworth"]["sampling_rate"] | |
config = config.model_validate({**config.model_dump(), **proc_config}) |
I think the primary need for this is to record the configs with the output files. So I think the key dump @sneakers-the-rat posted is pretty much enough to cover short-term needs. For the design of configs in general, the denoise and device configs probably have different needs. I'm not exactly sure what's a good interface that fits both:
|
i think recording the configs with the output is a really good idea - i would just like to spec that out a bit more explicitly :) that is the primary place that downstream analysis and data format tools will interact with this package, so it's worth having that be explicit and declarative. e.g. see the nwb miniscope extension - it's very dependent on the structure of the config model and output directories: https://github.com/catalystneuro/ndx-miniscope/tree/main/src/pynwb/ndx_miniscope
this is super true, and one of the reasons i decided it was worth the complexity to have an hard agree we need to have a GUI for this, and hopefully all the data modeling makes the GUI part a lot less work. a decent amount of what i've been thinking about re: making configs not care about paths is in prep for making GUIs on top of this package. just want to reiterate i totally understand and agree with the need for this PR, but think that we probably want to do it in a different way, and since i'm the one that's saying that i'm willing to put up the work to make that happen. i'll have increasing time to work on mio stuff in the coming weeks. |
I assume this PR is going to be more controversial
RROR tests/test_io.py
ERROR tests/test_models/test_model_process.py
ERROR tests/test_stream_daq.py
and here I probably need help. Because I don't know how to integrate it exactly or whats best practice.
mio process denoise -i video.avi -c denoise_example -u 'relativepath to' processing_log.json
the -u stand for update and uses the saved.json file
one can still run just the normal command without updating the config yaml and just using whatever its in there
`mio process denoise -i video.avi -c denoise_example'
this all works well on my end by just playing around with changing thing in the json and using -u to update it on a next run.
But I am falling test since changing the CLI probably doesn't make all the tests happy. But let me know
📚 Documentation preview 📚: https://miniscope-io--108.org.readthedocs.build/en/108/